import org.sonar.core.template.LoadedTemplateDao;
import org.sonar.core.user.AuthorDao;
import org.sonar.core.user.AuthorizationDao;
+import org.sonar.core.user.GroupMembershipDao;
import org.sonar.server.activity.db.ActivityDao;
import org.sonar.server.component.db.ComponentDao;
import org.sonar.server.component.db.ComponentIndexDao;
private final UserDao userDao;
private final GroupDao groupDao;
private final UserGroupDao userGroupDao;
+ private final GroupMembershipDao groupMembershipDao;
private final IssueDao issueDao;
private final IssueFilterDao issueFilterDao;
private final IssueChangeDao issueChangeDao;
userDao = getDao(map, UserDao.class);
groupDao = getDao(map, GroupDao.class);
userGroupDao = getDao(map, UserGroupDao.class);
+ groupMembershipDao = getDao(map, GroupMembershipDao.class);
issueDao = getDao(map, IssueDao.class);
issueFilterDao = getDao(map, IssueFilterDao.class);
issueChangeDao = getDao(map, IssueChangeDao.class);
return userGroupDao;
}
+ public GroupMembershipDao groupMembershipDao() {
+ return groupMembershipDao;
+ }
+
public ActionPlanDao actionPlanDao() {
return actionPlanDao;
}
pico.addSingleton(org.sonar.server.user.ws.ChangePasswordAction.class);
pico.addSingleton(org.sonar.server.user.ws.CurrentUserAction.class);
pico.addSingleton(org.sonar.server.user.ws.SearchAction.class);
+ pico.addSingleton(org.sonar.server.user.ws.GroupsAction.class);
pico.addSingleton(org.sonar.server.issue.ws.AuthorsAction.class);
pico.addSingleton(FavoritesWs.class);
pico.addSingleton(UserPropertiesWs.class);
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.user.ws;
+
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService.NewAction;
+import org.sonar.api.server.ws.WebService.NewController;
+import org.sonar.api.server.ws.WebService.Param;
+import org.sonar.api.utils.Paging;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.user.GroupMembershipDto;
+import org.sonar.core.user.GroupMembershipQuery;
+import org.sonar.core.user.UserDto;
+import org.sonar.server.db.DbClient;
+
+import javax.annotation.Nullable;
+
+import java.util.List;
+
+public class GroupsAction implements BaseUsersWsAction {
+
+ private static final String PARAM_LOGIN = "login";
+ private static final String PARAM_SELECTED = "selected";
+ private static final String PARAM_QUERY = "q";
+
+ private static final String SELECTION_ALL = "all";
+ private static final String SELECTION_SELECTED = "selected";
+ private static final String SELECTION_DESELECTED = "deselected";
+
+ private final DbClient dbClient;
+
+ public GroupsAction(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+
+ @Override
+ public void define(NewController context) {
+ NewAction action = context.createAction("groups")
+ .setDescription("List the groups a user belongs to.")
+ .setHandler(this)
+ .setResponseExample(getClass().getResource("example-groups.json"))
+ .setSince("5.2");
+
+ action.createParam(PARAM_LOGIN)
+ .setDescription("A user login")
+ .setExampleValue("admin")
+ .setRequired(true);
+
+ action.createParam(PARAM_SELECTED)
+ .setDescription("If specified, only show groups the user is member of (selected) or not (deselected).")
+ .setPossibleValues(SELECTION_SELECTED, SELECTION_DESELECTED, SELECTION_ALL)
+ .setDefaultValue(SELECTION_ALL);
+
+ action.createParam(PARAM_QUERY)
+ .setDescription("If specified, only show groups whose name contains the query.")
+ .setExampleValue("user");
+
+ action.addPagingParams(25);
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ String login = request.mandatoryParam(PARAM_LOGIN);
+ int pageSize = request.mandatoryParamAsInt(Param.PAGE_SIZE);
+ int page = request.mandatoryParamAsInt(Param.PAGE);
+ String queryString = request.param(PARAM_QUERY);
+ String selected = request.param(PARAM_SELECTED);
+
+ GroupMembershipQuery query = GroupMembershipQuery.builder()
+ .login(login)
+ .groupSearch(queryString)
+ .membership(getMembership(selected))
+ .pageIndex(page)
+ .pageSize(pageSize)
+ .build();
+
+ DbSession session = dbClient.openSession(false);
+ try {
+ UserDto user = dbClient.userDao().selectByLogin(session, login);
+ int total = dbClient.groupMembershipDao().countGroups(session, query, user.getId());
+ Paging paging = Paging.create(pageSize, page, total);
+ List<GroupMembershipDto> groups = dbClient.groupMembershipDao().selectGroups(session, query, user.getId(), paging.offset(), pageSize);
+
+ JsonWriter json = response.newJsonWriter().beginObject();
+ writeGroups(json, groups);
+ writePaging(json, paging);
+ json.endObject().close();
+ } finally {
+ session.close();
+ }
+ }
+
+ private void writeGroups(JsonWriter json, List<GroupMembershipDto> groups) {
+ json.name("groups").beginArray();
+ for (GroupMembershipDto group : groups) {
+ json.beginObject()
+ .prop("name", group.getName())
+ .prop("description", group.getDescription())
+ .prop("selected", group.getUserId() != null)
+ .endObject();
+ }
+ json.endArray();
+ }
+
+ private void writePaging(JsonWriter json, Paging paging) {
+ json.prop("p", paging.pageIndex())
+ .prop("ps", paging.pageSize())
+ .prop("total", paging.total());
+ }
+
+ private String getMembership(@Nullable String selected) {
+ String membership = GroupMembershipQuery.ANY;
+ if (SELECTION_SELECTED.equals(selected)) {
+ membership = GroupMembershipQuery.IN;
+ } else if (SELECTION_DESELECTED.equals(selected)) {
+ membership = GroupMembershipQuery.OUT;
+ }
+ return membership;
+ }
+}
--- /dev/null
+{
+ "p": 1,
+ "ps": 25,
+ "total": 2,
+ "groups": [
+ {"name": "sonar-admins", "description": "Sonar Admins", "selected": false},
+ {"name": "sonar-users", "description": "Sonar Users", "selected": true}
+ ]
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.user.ws;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.System2;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.core.user.GroupDto;
+import org.sonar.core.user.GroupMembershipDao;
+import org.sonar.core.user.UserDto;
+import org.sonar.core.user.UserGroupDto;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.user.db.GroupDao;
+import org.sonar.server.user.db.UserDao;
+import org.sonar.server.user.db.UserGroupDao;
+import org.sonar.server.ws.WsTester;
+
+public class GroupsActionTest {
+
+ @ClassRule
+ public static final DbTester dbTester = new DbTester();
+
+ WebService.Controller controller;
+
+ WsTester tester;
+
+ DbClient dbClient;
+
+ DbSession session;
+
+ @Before
+ public void setUp() throws Exception {
+ dbTester.truncateTables();
+
+ System2 system2 = new System2();
+ UserDao userDao = new UserDao(dbTester.myBatis(), system2);
+ GroupDao groupDao = new GroupDao(system2);
+ UserGroupDao userGroupDao = new UserGroupDao();
+ GroupMembershipDao groupMembershipDao = new GroupMembershipDao(dbTester.myBatis());
+
+ dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), userDao, groupDao, userGroupDao, groupMembershipDao);
+ session = dbClient.openSession(false);
+ session.commit();
+
+ tester = new WsTester(new UsersWs(new GroupsAction(dbClient)));
+ controller = tester.controller("api/users");
+
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ session.close();
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void fail_on_unknown_user() throws Exception {
+ tester.newGetRequest("api/users", "groups")
+ .setParam("login", "john").execute();
+ }
+
+ @Test
+ public void empty_groups() throws Exception {
+ createUser();
+ session.commit();
+
+ tester.newGetRequest("api/users", "groups")
+ .setParam("login", "john")
+ .execute()
+ .assertJson(getClass(), "empty.json");
+ }
+
+ @Test
+ public void all_groups() throws Exception {
+ UserDto user = createUser();
+ GroupDto usersGroup = createGroup("sonar-users", "Sonar Users");
+ createGroup("sonar-admins", "Sonar Admins");
+ addUserToGroup(user, usersGroup);
+ session.commit();
+
+ tester.newGetRequest("api/users", "groups")
+ .setParam("login", "john")
+ .execute()
+ .assertJson(getClass(), "all.json");
+ }
+
+ @Test
+ public void selected_groups() throws Exception {
+ UserDto user = createUser();
+ GroupDto usersGroup = createGroup("sonar-users", "Sonar Users");
+ createGroup("sonar-admins", "Sonar Admins");
+ addUserToGroup(user, usersGroup);
+ session.commit();
+
+ tester.newGetRequest("api/users", "groups")
+ .setParam("login", "john")
+ .setParam("selected", "selected")
+ .execute()
+ .assertJson(getClass(), "selected.json");
+ }
+
+ @Test
+ public void deselected_groups() throws Exception {
+ UserDto user = createUser();
+ GroupDto usersGroup = createGroup("sonar-users", "Sonar Users");
+ createGroup("sonar-admins", "Sonar Admins");
+ addUserToGroup(user, usersGroup);
+ session.commit();
+
+ tester.newGetRequest("api/users", "groups")
+ .setParam("login", "john")
+ .setParam("selected", "deselected")
+ .execute()
+ .assertJson(getClass(), "deselected.json");
+ }
+
+ private UserDto createUser() {
+ return dbClient.userDao().insert(session, new UserDto()
+ .setActive(true)
+ .setEmail("john@email.com")
+ .setLogin("john")
+ .setName("John")
+ .setScmAccounts("jn"));
+ }
+
+ @Test
+ public void paging() throws Exception {
+ UserDto user = createUser();
+ GroupDto usersGroup = createGroup("sonar-users", "Sonar Users");
+ createGroup("sonar-admins", "Sonar Admins");
+ addUserToGroup(user, usersGroup);
+ session.commit();
+
+ tester.newGetRequest("api/users", "groups")
+ .setParam("login", "john")
+ .setParam("ps", "1")
+ .execute()
+ .assertJson(getClass(), "all_page1.json");
+
+ tester.newGetRequest("api/users", "groups")
+ .setParam("login", "john")
+ .setParam("ps", "1")
+ .setParam("p", "2")
+ .execute()
+ .assertJson(getClass(), "all_page2.json");
+ }
+
+ @Test
+ public void filtering() throws Exception {
+ UserDto user = createUser();
+ GroupDto usersGroup = createGroup("sonar-users", "Sonar Users");
+ createGroup("sonar-admins", "Sonar Admins");
+ addUserToGroup(user, usersGroup);
+ session.commit();
+
+ tester.newGetRequest("api/users", "groups")
+ .setParam("login", "john")
+ .setParam("q", "users")
+ .execute()
+ .assertJson(getClass(), "all_users.json");
+
+ tester.newGetRequest("api/users", "groups")
+ .setParam("login", "john")
+ .setParam("q", "admin")
+ .execute()
+ .assertJson(getClass(), "all_admin.json");
+ }
+
+ private GroupDto createGroup(String name, String description) {
+ return dbClient.groupDao().insert(session, new GroupDto().setName(name).setDescription(description));
+ }
+
+ private void addUserToGroup(UserDto user, GroupDto usersGroup) {
+ dbClient.userGroupDao().insert(session, new UserGroupDto().setUserId(user.getId()).setGroupId(usersGroup.getId()));
+ }
+}
--- /dev/null
+{
+ "p": 1,
+ "total": 2,
+ "groups": [
+ {"name": "sonar-admins", "description": "Sonar Admins", "selected": false},
+ {"name": "sonar-users", "description": "Sonar Users", "selected": true}
+ ]
+}
\ No newline at end of file
--- /dev/null
+{
+ "p": 1,
+ "total": 1,
+ "groups": [
+ {"name": "sonar-admins", "description": "Sonar Admins", "selected": false}
+ ]
+}
\ No newline at end of file
--- /dev/null
+{
+ "p": 1,
+ "ps": 1,
+ "total": 2,
+ "groups": [
+ {"name": "sonar-admins", "description": "Sonar Admins", "selected": false}
+ ]
+}
\ No newline at end of file
--- /dev/null
+{
+ "p": 2,
+ "ps": 1,
+ "total": 2,
+ "groups": [
+ {"name": "sonar-users", "description": "Sonar Users", "selected": true}
+ ]
+}
\ No newline at end of file
--- /dev/null
+{
+ "p": 1,
+ "total": 1,
+ "groups": [
+ {"name": "sonar-users", "description": "Sonar Users", "selected": true}
+ ]
+}
\ No newline at end of file
--- /dev/null
+{
+ "p": 1,
+ "total": 1,
+ "groups": [
+ {"name": "sonar-admins", "description": "Sonar Admins", "selected": false}
+ ]
+}
\ No newline at end of file
--- /dev/null
+{
+ "p": 1,
+ "total": 0,
+ "groups": []
+}
\ No newline at end of file
--- /dev/null
+{
+ "p": 1,
+ "total": 1,
+ "groups": [
+ {"name": "sonar-users", "description": "Sonar Users", "selected": true}
+ ]
+}
\ No newline at end of file
import com.google.common.collect.ImmutableMap;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
+import org.sonar.core.persistence.DaoComponent;
import org.sonar.core.persistence.MyBatis;
import java.util.List;
import java.util.Map;
-public class GroupMembershipDao {
+public class GroupMembershipDao implements DaoComponent {
private final MyBatis mybatis;
this.mybatis = mybatis;
}
+ // TODO Remove this method and associated client code when the UI is migrated to Backbone
public List<GroupMembershipDto> selectGroups(GroupMembershipQuery query, Long userId, int offset, int limit) {
SqlSession session = mybatis.openSession(false);
try {
- Map<String, Object> params = ImmutableMap.of("query", query, "userId", userId);
- return session.selectList("org.sonar.core.user.GroupMembershipMapper.selectGroups", params, new RowBounds(offset, limit));
+ return selectGroups(session, query, userId, offset, limit);
} finally {
MyBatis.closeQuietly(session);
}
}
+ public List<GroupMembershipDto> selectGroups(SqlSession session, GroupMembershipQuery query, Long userId, int offset, int limit) {
+ Map<String, Object> params = ImmutableMap.of("query", query, "userId", userId);
+ return session.selectList("org.sonar.core.user.GroupMembershipMapper.selectGroups", params, new RowBounds(offset, limit));
+ }
+
+ public int countGroups(SqlSession session, GroupMembershipQuery query, Long userId) {
+ Map<String, Object> params = ImmutableMap.of("query", query, "userId", userId);
+ return session.selectOne("org.sonar.core.user.GroupMembershipMapper.countGroups", params);
+ }
+
@VisibleForTesting
List<GroupMembershipDto> selectGroups(GroupMembershipQuery query, Long userId) {
return selectGroups(query, userId, 0, Integer.MAX_VALUE);
<mapper namespace="org.sonar.core.user.GroupMembershipMapper">
- <select id="selectGroups" parameterType="map" resultType="GroupMembership">
- SELECT g.id as id, g.name as name, g.description as description, gu.user_id as userId
+ <sql id="commonClauses">
FROM groups g
LEFT JOIN groups_users gu ON gu.group_id=g.id AND gu.user_id=#{userId}
<where>
AND (UPPER(g.name) LIKE #{query.groupSearchSql} ESCAPE '/')
</if>
</where>
+ </sql>
+
+ <select id="selectGroups" parameterType="map" resultType="GroupMembership">
+ SELECT g.id as id, g.name as name, g.description as description, gu.user_id as userId
+ <include refid="commonClauses" />
ORDER BY g.name
</select>
+ <select id="countGroups" parameterType="map" resultType="int">
+ SELECT COUNT(g.id)
+ <include refid="commonClauses" />
+ </select>
+
</mapper>
package org.sonar.core.user;
import org.junit.Before;
+import org.junit.ClassRule;
import org.junit.Test;
-import org.sonar.core.persistence.AbstractDaoTestCase;
+import org.junit.experimental.categories.Category;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.test.DbTests;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
-public class GroupMembershipDaoTest extends AbstractDaoTestCase {
+@Category(DbTests.class)
+public class GroupMembershipDaoTest {
+
+ @ClassRule
+ public static final DbTester dbTester = new DbTester();
private GroupMembershipDao dao;
@Before
public void setUp() {
- dao = new GroupMembershipDao(getMyBatis());
+ dbTester.truncateTables();
+ dao = new GroupMembershipDao(dbTester.myBatis());
}
@Test
public void select_all_groups_by_query() throws Exception {
- setupData("shared");
+ dbTester.prepareDbUnit(getClass(), "shared.xml");
GroupMembershipQuery query = GroupMembershipQuery.builder().login("arthur").build();
List<GroupMembershipDto> result = dao.selectGroups(query, 200L);
@Test
public void select_user_group() throws Exception {
- setupData("select_user_group");
+ dbTester.prepareDbUnit(getClass(), "select_user_group.xml");
GroupMembershipQuery query = GroupMembershipQuery.builder().login("arthur").build();
List<GroupMembershipDto> result = dao.selectGroups(query, 201L);
@Test
public void select_user_groups_by_query() throws Exception {
- setupData("shared");
+ dbTester.prepareDbUnit(getClass(), "shared.xml");
// 200 is member of 3 groups
assertThat(dao.selectGroups(GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.IN).build(), 200L)).hasSize(3);
@Test
public void select_groups_not_affected_to_a_user_by_query() throws Exception {
- setupData("shared");
+ dbTester.prepareDbUnit(getClass(), "shared.xml");
// 200 is member of 3 groups
assertThat(dao.selectGroups(GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.OUT).build(), 200L)).isEmpty();
@Test
public void search_by_group_name() throws Exception {
- setupData("shared");
+ dbTester.prepareDbUnit(getClass(), "shared.xml");
List<GroupMembershipDto> result = dao.selectGroups(GroupMembershipQuery.builder().login("arthur").groupSearch("user").build(), 200L);
assertThat(result).hasSize(1);
@Test
public void search_by_group_name_with_capitalization() throws Exception {
- setupData("shared");
+ dbTester.prepareDbUnit(getClass(), "shared.xml");
List<GroupMembershipDto> result = dao.selectGroups(GroupMembershipQuery.builder().login("arthur").groupSearch("UsER").build(), 200L);
assertThat(result).hasSize(1);
@Test
public void should_be_sorted_by_group_name() throws Exception {
- setupData("should_be_sorted_by_group_name");
+ dbTester.prepareDbUnit(getClass(), "should_be_sorted_by_group_name.xml");
List<GroupMembershipDto> result = dao.selectGroups(GroupMembershipQuery.builder().login("arthur").build(), 200L);
assertThat(result).hasSize(3);
@Test
public void should_be_paginated() throws Exception {
- setupData("shared");
+ dbTester.prepareDbUnit(getClass(), "shared.xml");
List<GroupMembershipDto> result = dao.selectGroups(GroupMembershipQuery.builder().login("arthur").build(), 200L, 0, 2);
assertThat(result).hasSize(2);
assertThat(result.get(0).getName()).isEqualTo("sonar-users");
}
+ @Test
+ public void count_groups() throws Exception {
+ dbTester.prepareDbUnit(getClass(), "shared.xml");
+ DbSession session = dbTester.myBatis().openSession(false);
+
+ try {
+ // 200 is member of 3 groups
+ assertThat(dao.countGroups(session, GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.IN).build(), 200L)).isEqualTo(3);
+ assertThat(dao.countGroups(session, GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.OUT).build(), 200L)).isZero();
+ // 201 is member of 1 group on 3
+ assertThat(dao.countGroups(session, GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.IN).build(), 201L)).isEqualTo(1);
+ assertThat(dao.countGroups(session, GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.OUT).build(), 201L)).isEqualTo(2);
+ // 999 is member of 0 group
+ assertThat(dao.countGroups(session, GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.IN).build(), 999L)).isZero();
+ assertThat(dao.countGroups(session, GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.OUT).build(), 2999L)).isEqualTo(3);
+ } finally {
+ session.close();
+ }
+ }
}