From b4e4af137329f4c90e6365e233cdcba3a5322697 Mon Sep 17 00:00:00 2001 From: Teryk Bellahsene Date: Tue, 4 Aug 2015 15:29:06 +0200 Subject: [PATCH] SONAR-6479 WS permissions/users search users with a specific permission --- .../server/permission/PermissionFinder.java | 57 +- .../permission/PermissionQueryParser.java | 13 +- .../server/permission/PermissionService.java | 6 +- .../UserWithPermissionQueryResult.java | 19 +- .../permission/ws/PermissionsWsModule.java | 3 +- .../server/permission/ws/UsersAction.java | 126 ++ .../server/permission/ws/users-example.json | 20 + .../permission/PermissionFinderTest.java | 89 +- .../ws/PermissionsWsModuleTest.java | 2 +- .../server/permission/ws/UsersActionTest.java | 175 ++ .../org/sonar/server/ws/TestResponse.java | 1 - .../permission/ws/UsersActionTest/users.json | 19 + .../sonar/db/permission/PermissionDao.java | 31 +- .../sonar/db/permission/PermissionMapper.java | 2 + .../db/permission/PermissionTemplateDao.java | 24 +- .../permission/PermissionTemplateMapper.java | 2 + .../sonar/db/permission/PermissionMapper.xml | 13 +- .../permission/PermissionTemplateMapper.xml | 13 +- .../permission/PermissionTemplateDaoTest.java | 3 + .../permission/UserWithPermissionDaoTest.java | 30 +- .../java/org/sonar/db/user/UserTesting.java | 33 + .../org/sonar/api/server/ws/WebService.java | 8 +- .../org/sonarqube/ws/Permissions.java | 1655 +++++++++++++++++ .../src/main/protobuf/ws-permissions.proto | 40 + 24 files changed, 2258 insertions(+), 126 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/permission/ws/UsersAction.java create mode 100644 server/sonar-server/src/main/resources/org/sonar/server/permission/ws/users-example.json create mode 100644 server/sonar-server/src/test/java/org/sonar/server/permission/ws/UsersActionTest.java create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/permission/ws/UsersActionTest/users.json create mode 100644 sonar-db/src/test/java/org/sonar/db/user/UserTesting.java create mode 100644 sonar-ws/src/main/gen-java/org/sonarqube/ws/Permissions.java create mode 100644 sonar-ws/src/main/protobuf/ws-permissions.proto diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionFinder.java b/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionFinder.java index 026c6adff65..c9aa9271405 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionFinder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionFinder.java @@ -30,16 +30,18 @@ import org.sonar.api.security.DefaultGroups; import org.sonar.api.server.ServerSide; import org.sonar.api.utils.Paging; import org.sonar.core.permission.GroupWithPermission; +import org.sonar.core.permission.UserWithPermission; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ResourceDao; +import org.sonar.db.component.ResourceDto; +import org.sonar.db.component.ResourceQuery; import org.sonar.db.permission.GroupWithPermissionDto; import org.sonar.db.permission.PermissionDao; import org.sonar.db.permission.PermissionQuery; import org.sonar.db.permission.PermissionTemplateDao; import org.sonar.db.permission.PermissionTemplateDto; -import org.sonar.core.permission.UserWithPermission; import org.sonar.db.permission.UserWithPermissionDto; -import org.sonar.db.component.ResourceDao; -import org.sonar.db.component.ResourceDto; -import org.sonar.db.component.ResourceQuery; import org.sonar.server.exceptions.NotFoundException; import static com.google.common.collect.Lists.newArrayList; @@ -47,27 +49,41 @@ import static com.google.common.collect.Lists.newArrayList; @ServerSide public class PermissionFinder { + private final DbClient dbClient; + private final PermissionDao permissionDao; private final ResourceDao resourceDao; - private final PermissionTemplateDao permissionTemplateDao; - public PermissionFinder(PermissionDao permissionDao, ResourceDao resourceDao, PermissionTemplateDao permissionTemplateDao) { - this.resourceDao = resourceDao; - this.permissionDao = permissionDao; - this.permissionTemplateDao = permissionTemplateDao; + public PermissionFinder(DbClient dbClient) { + this.dbClient = dbClient; + this.resourceDao = dbClient.resourceDao(); + this.permissionDao = dbClient.permissionDao(); + this.permissionTemplateDao = dbClient.permissionTemplateDao(); } public UserWithPermissionQueryResult findUsersWithPermission(PermissionQuery query) { Long componentId = componentId(query.component()); - int limit = limit(query); - return toUserQueryResult(permissionDao.selectUsers(query, componentId, offset(query), limit), limit); + int limit = query.pageSize(); + DbSession dbSession = dbClient.openSession(false); + try { + int total = permissionDao.countUsers(dbSession, query, componentId); + return toUserQueryResult(permissionDao.selectUsers(dbSession, query, componentId, offset(query), limit), total); + } finally { + dbClient.closeSession(dbSession); + } } public UserWithPermissionQueryResult findUsersWithPermissionTemplate(PermissionQuery query) { Long permissionTemplateId = templateId(query.template()); - int limit = limit(query); - return toUserQueryResult(permissionTemplateDao.selectUsers(query, permissionTemplateId, offset(query), limit), limit); + int limit = query.pageSize(); + DbSession dbSession = dbClient.openSession(false); + try { + int total = permissionTemplateDao.countUsers(dbSession, query, permissionTemplateId); + return toUserQueryResult(permissionTemplateDao.selectUsers(dbSession, query, permissionTemplateId, offset(query), limit), total); + } finally { + dbClient.closeSession(dbSession); + } } /** @@ -86,14 +102,8 @@ public class PermissionFinder { return toGroupQueryResult(permissionTemplateDao.selectGroups(query, permissionTemplateId), query); } - private static UserWithPermissionQueryResult toUserQueryResult(List dtos, int limit) { - boolean hasMoreResults = false; - if (dtos.size() == limit) { - hasMoreResults = true; - // Removed last entry as it's only need to know if there more results or not - dtos.remove(dtos.size() - 1); - } - return new UserWithPermissionQueryResult(toUserWithPermissionList(dtos), hasMoreResults); + private static UserWithPermissionQueryResult toUserQueryResult(List dtos, int total) { + return new UserWithPermissionQueryResult(toUserWithPermissionList(dtos), total); } private static List toUserWithPermissionList(List dtos) { @@ -140,11 +150,6 @@ public class PermissionFinder { return (pageIndex - 1) * pageSize; } - private static int limit(PermissionQuery query) { - // Add one to page size in order to be able to know if there's more results or not - return query.pageSize() + 1; - } - private List filterMembership(List dtos, PermissionQuery query) { return newArrayList(Iterables.filter(dtos, new GroupWithPermissionMatchQuery(query))); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionQueryParser.java b/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionQueryParser.java index e8a5c67fa88..0b0fead64f8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionQueryParser.java +++ b/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionQueryParser.java @@ -20,17 +20,15 @@ package org.sonar.server.permission; +import java.util.Map; import org.sonar.api.server.ws.WebService.SelectionMode; - import org.sonar.db.permission.PermissionQuery; import org.sonar.db.user.GroupMembershipQuery; import org.sonar.server.util.RubyUtils; -import java.util.Map; - public class PermissionQueryParser { - private PermissionQueryParser(){ + private PermissionQueryParser() { // Utility class } @@ -39,15 +37,15 @@ public class PermissionQueryParser { builder.permission((String) params.get("permission")); builder.component((String) params.get("component")); builder.template((String) params.get("template")); - builder.membership(membership(params)); + builder.membership(toMembership((String) params.get("selected"))); builder.search((String) params.get("query")); builder.pageIndex(RubyUtils.toInteger(params.get("page"))); builder.pageSize(RubyUtils.toInteger(params.get("pageSize"))); return builder.build(); } - private static String membership(Map params) { - SelectionMode selectionMode = SelectionMode.fromParam((String) params.get("selected")); + public static String toMembership(String selectionModeString) { + SelectionMode selectionMode = SelectionMode.fromParam(selectionModeString); if (SelectionMode.SELECTED == selectionMode) { return GroupMembershipQuery.IN; } else if (SelectionMode.DESELECTED == selectionMode) { @@ -57,5 +55,4 @@ public class PermissionQueryParser { } } - } diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionService.java b/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionService.java index feef6db4848..28ad37a2955 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionService.java @@ -47,10 +47,11 @@ import org.sonar.server.user.UserSession; @ServerSide public class PermissionService { + + private enum Operation { - ADD, REMOVE + ADD, REMOVE; } - private static final String OBJECT_TYPE_USER = "User"; private static final String OBJECT_TYPE_GROUP = "Group"; private static final String NOT_FOUND_FORMAT = "%s %s does not exist"; @@ -61,7 +62,6 @@ public class PermissionService { private final IssueAuthorizationIndexer issueAuthorizationIndexer; private final UserSession userSession; private final ComponentFinder componentFinder; - public PermissionService(DbClient dbClient, PermissionRepository permissionRepository, PermissionFinder finder, IssueAuthorizationIndexer issueAuthorizationIndexer, UserSession userSession, ComponentFinder componentFinder) { this.dbClient = dbClient; diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/UserWithPermissionQueryResult.java b/server/sonar-server/src/main/java/org/sonar/server/permission/UserWithPermissionQueryResult.java index 621ce03ef5c..aec170fb6e5 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/UserWithPermissionQueryResult.java +++ b/server/sonar-server/src/main/java/org/sonar/server/permission/UserWithPermissionQueryResult.java @@ -20,26 +20,31 @@ package org.sonar.server.permission; -import org.sonar.core.permission.UserWithPermission; - import java.util.List; +import org.sonar.core.permission.UserWithPermission; public class UserWithPermissionQueryResult { - private List users; - private boolean hasMoreResults; + private final List users; + private final int total; + private final boolean hasMoreResults; - public UserWithPermissionQueryResult(List users, boolean hasMoreResults) { + public UserWithPermissionQueryResult(List users, int total) { this.users = users; - this.hasMoreResults = hasMoreResults; + this.total = total; + this.hasMoreResults = total > users.size(); } public List users() { return users; } + public int total() { + return total; + } + + // called by Ruby Code public boolean hasMoreResults() { return hasMoreResults; } - } diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionsWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionsWsModule.java index 246f172056b..b69487adaa8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionsWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionsWsModule.java @@ -30,6 +30,7 @@ public class PermissionsWsModule extends Module { AddGroupAction.class, AddUserAction.class, RemoveGroupAction.class, - RemoveUserAction.class); + RemoveUserAction.class, + UsersAction.class); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/ws/UsersAction.java b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/UsersAction.java new file mode 100644 index 00000000000..fc3cafe0e2d --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/UsersAction.java @@ -0,0 +1,126 @@ +/* + * 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.permission.ws; + +import com.google.common.io.Resources; +import java.util.List; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.server.ws.WebService.Param; +import org.sonar.api.server.ws.WebService.SelectionMode; +import org.sonar.api.utils.text.JsonWriter; +import org.sonar.core.permission.GlobalPermissions; +import org.sonar.core.permission.UserWithPermission; +import org.sonar.core.util.ProtobufJsonFormat; +import org.sonar.db.permission.PermissionQuery; +import org.sonar.server.permission.PermissionFinder; +import org.sonar.server.permission.UserWithPermissionQueryResult; +import org.sonar.server.plugins.MimeTypes; +import org.sonar.server.user.UserSession; +import org.sonarqube.ws.Common; +import org.sonarqube.ws.Permissions; + +import static com.google.common.base.Objects.firstNonNull; +import static org.sonar.server.permission.PermissionQueryParser.toMembership; + +public class UsersAction implements PermissionsWsAction { + + private final UserSession userSession; + private final PermissionFinder permissionFinder; + + public UsersAction(UserSession userSession, PermissionFinder permissionFinder) { + this.userSession = userSession; + this.permissionFinder = permissionFinder; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("users") + .setSince("5.2") + .setDescription(String.format("List permission's users.
" + + "If the query parameter '%s' is specified, the '%s' parameter is '%s'.", + Param.TEXT_QUERY, Param.SELECTED, SelectionMode.ALL.value())) + .addPagingParams(100) + .addSearchQuery("stas", "names") + .addSelectionModeParam() + .setInternal(true) + .setResponseExample(Resources.getResource(getClass(), "users-example.json")) + .setHandler(this); + + action.createParam("permission") + .setExampleValue("scan") + .setRequired(true) + .setPossibleValues(GlobalPermissions.ALL); + } + + @Override + public void handle(Request request, Response response) throws Exception { + String permission = request.mandatoryParam("permission"); + String selected = request.param(Param.SELECTED); + int page = request.mandatoryParamAsInt(Param.PAGE); + int pageSize = request.mandatoryParamAsInt(Param.PAGE_SIZE); + String query = request.param(Param.TEXT_QUERY); + if (query != null) { + selected = SelectionMode.ALL.value(); + } + + userSession + .checkLoggedIn() + .checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN); + + PermissionQuery.Builder permissionQuery = PermissionQuery.builder() + .permission(permission) + .pageIndex(page) + .pageSize(pageSize) + .membership(toMembership(firstNonNull(selected, SelectionMode.SELECTED.value()))); + if (query != null) { + permissionQuery.search(query); + } + + UserWithPermissionQueryResult usersResult = permissionFinder.findUsersWithPermission(permissionQuery.build()); + List usersWithPermission = usersResult.users(); + + Permissions.UsersResponse.Builder userResponse = Permissions.UsersResponse.newBuilder(); + Permissions.UsersResponse.User.Builder user = Permissions.UsersResponse.User.newBuilder(); + Common.Paging.Builder paging = Common.Paging.newBuilder(); + for (UserWithPermission userWithPermission : usersWithPermission) { + userResponse.addUsers( + user + .clear() + .setLogin(userWithPermission.login()) + .setName(userWithPermission.name()) + .setSelected(userWithPermission.hasPermission())); + userResponse.setPaging( + paging + .clear() + .setPages(page) + .setPageSize(pageSize) + .setTotal(usersResult.total()) + ); + } + + response.stream().setMediaType(MimeTypes.JSON); + JsonWriter json = response.newJsonWriter(); + ProtobufJsonFormat.write(userResponse.build(), json); + json.close(); + } +} diff --git a/server/sonar-server/src/main/resources/org/sonar/server/permission/ws/users-example.json b/server/sonar-server/src/main/resources/org/sonar/server/permission/ws/users-example.json new file mode 100644 index 00000000000..b4bd9daa6b3 --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/permission/ws/users-example.json @@ -0,0 +1,20 @@ +{ + "users": [ + { + "login": "admin", + "name": "Administrator", + "selected": true + }, + { + "login": "george.orwell", + "name": "George Orwell", + "selected": true + } + ], + "paging": { + "pageSize": 100, + "total": 2, + "pages": 1 + } +} + diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/PermissionFinderTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/PermissionFinderTest.java index 293092c9127..440878a0c8b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/permission/PermissionFinderTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/permission/PermissionFinderTest.java @@ -27,15 +27,17 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.sonar.core.permission.GroupWithPermission; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ResourceDao; +import org.sonar.db.component.ResourceDto; +import org.sonar.db.component.ResourceQuery; import org.sonar.db.permission.GroupWithPermissionDto; import org.sonar.db.permission.PermissionDao; import org.sonar.db.permission.PermissionQuery; import org.sonar.db.permission.PermissionTemplateDao; import org.sonar.db.permission.PermissionTemplateDto; import org.sonar.db.permission.UserWithPermissionDto; -import org.sonar.db.component.ResourceDao; -import org.sonar.db.component.ResourceDto; -import org.sonar.db.component.ResourceQuery; import org.sonar.server.exceptions.NotFoundException; import static com.google.common.collect.Lists.newArrayList; @@ -45,6 +47,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -60,23 +63,28 @@ public class PermissionFinderTest { @Mock PermissionTemplateDao permissionTemplateDao; - PermissionFinder finder; + PermissionFinder underTest; @Before public void setUp() { + DbClient dbClient = mock(DbClient.class); + when(dbClient.resourceDao()).thenReturn(resourceDao); + when(dbClient.permissionDao()).thenReturn(permissionDao); + when(dbClient.permissionTemplateDao()).thenReturn(permissionTemplateDao); when(resourceDao.selectResource(any(ResourceQuery.class))).thenReturn(new ResourceDto().setId(100L).setName("org.sample.Sample")); - finder = new PermissionFinder(permissionDao, resourceDao, permissionTemplateDao); + underTest = new PermissionFinder(dbClient); } @Test public void find_users() { - when(permissionDao.selectUsers(any(PermissionQuery.class), anyLong(), anyInt(), anyInt())).thenReturn( + when(permissionDao.selectUsers(any(DbSession.class), any(PermissionQuery.class), anyLong(), anyInt(), anyInt())).thenReturn( newArrayList(new UserWithPermissionDto().setName("user1").setPermission("user")) ); + when(permissionDao.countUsers(any(DbSession.class), any(PermissionQuery.class), anyLong())).thenReturn(1); - UserWithPermissionQueryResult result = finder.findUsersWithPermission(PermissionQuery.builder().permission("user").build()); + UserWithPermissionQueryResult result = underTest.findUsersWithPermission(PermissionQuery.builder().permission("user").build()); assertThat(result.users()).hasSize(1); - assertThat(result.hasMoreResults()).isFalse(); + assertThat(result.total()).isEqualTo(1); } @Test @@ -84,7 +92,7 @@ public class PermissionFinderTest { when(resourceDao.selectResource(any(ResourceQuery.class))).thenReturn(null); try { - finder.findUsersWithPermission(PermissionQuery.builder().permission("user").component("Unknown").build()); + underTest.findUsersWithPermission(PermissionQuery.builder().permission("user").component("Unknown").build()); fail(); } catch (Exception e) { assertThat(e).isInstanceOf(NotFoundException.class).hasMessage("Component 'Unknown' does not exist"); @@ -93,51 +101,54 @@ public class PermissionFinderTest { @Test public void find_users_with_paging() { - finder.findUsersWithPermission(PermissionQuery.builder().permission("user").pageIndex(3).pageSize(10).build()); + underTest.findUsersWithPermission(PermissionQuery.builder().permission("user").pageIndex(3).pageSize(10).build()); ArgumentCaptor argumentOffset = ArgumentCaptor.forClass(Integer.class); ArgumentCaptor argumentLimit = ArgumentCaptor.forClass(Integer.class); - verify(permissionDao).selectUsers(any(PermissionQuery.class), anyLong(), argumentOffset.capture(), argumentLimit.capture()); + verify(permissionDao).selectUsers(any(DbSession.class), any(PermissionQuery.class), anyLong(), argumentOffset.capture(), argumentLimit.capture()); assertThat(argumentOffset.getValue()).isEqualTo(20); - assertThat(argumentLimit.getValue()).isEqualTo(11); + assertThat(argumentLimit.getValue()).isEqualTo(10); } @Test public void find_users_with_paging_having_more_results() { - when(permissionDao.selectUsers(any(PermissionQuery.class), anyLong(), anyInt(), anyInt())).thenReturn(newArrayList( + when(permissionDao.selectUsers(any(DbSession.class), any(PermissionQuery.class), anyLong(), anyInt(), anyInt())).thenReturn(newArrayList( new UserWithPermissionDto().setName("user1").setPermission("user"), new UserWithPermissionDto().setName("user2").setPermission("user"), new UserWithPermissionDto().setName("user3").setPermission("user")) ); - UserWithPermissionQueryResult result = finder.findUsersWithPermission(PermissionQuery.builder().permission("user").pageIndex(1).pageSize(2).build()); + when(permissionDao.countUsers(any(DbSession.class), any(PermissionQuery.class), anyLong())).thenReturn(3); + UserWithPermissionQueryResult result = underTest.findUsersWithPermission(PermissionQuery.builder().permission("user").pageIndex(1).pageSize(2).build()); ArgumentCaptor argumentOffset = ArgumentCaptor.forClass(Integer.class); ArgumentCaptor argumentLimit = ArgumentCaptor.forClass(Integer.class); - verify(permissionDao).selectUsers(any(PermissionQuery.class), anyLong(), argumentOffset.capture(), argumentLimit.capture()); + verify(permissionDao).selectUsers(any(DbSession.class), any(PermissionQuery.class), anyLong(), argumentOffset.capture(), argumentLimit.capture()); assertThat(argumentOffset.getValue()).isEqualTo(0); - assertThat(argumentLimit.getValue()).isEqualTo(3); - assertThat(result.hasMoreResults()).isTrue(); + assertThat(argumentLimit.getValue()).isEqualTo(2); + assertThat(result.total()).isEqualTo(3); } @Test public void find_users_with_paging_having_no_more_results() { - when(permissionDao.selectUsers(any(PermissionQuery.class), anyLong(), anyInt(), anyInt())).thenReturn(newArrayList( + when(permissionDao.selectUsers(any(DbSession.class), any(PermissionQuery.class), anyLong(), anyInt(), anyInt())).thenReturn(newArrayList( new UserWithPermissionDto().setName("user1").setPermission("user"), new UserWithPermissionDto().setName("user2").setPermission("user"), new UserWithPermissionDto().setName("user4").setPermission("user"), new UserWithPermissionDto().setName("user3").setPermission("user")) ); - UserWithPermissionQueryResult result = finder.findUsersWithPermission(PermissionQuery.builder().permission("user").pageIndex(1).pageSize(10).build()); + when(permissionDao.countUsers(any(DbSession.class), any(PermissionQuery.class), anyLong())).thenReturn(4); + + UserWithPermissionQueryResult result = underTest.findUsersWithPermission(PermissionQuery.builder().permission("user").pageIndex(1).pageSize(10).build()); ArgumentCaptor argumentOffset = ArgumentCaptor.forClass(Integer.class); ArgumentCaptor argumentLimit = ArgumentCaptor.forClass(Integer.class); - verify(permissionDao).selectUsers(any(PermissionQuery.class), anyLong(), argumentOffset.capture(), argumentLimit.capture()); + verify(permissionDao).selectUsers(any(DbSession.class), any(PermissionQuery.class), anyLong(), argumentOffset.capture(), argumentLimit.capture()); assertThat(argumentOffset.getValue()).isEqualTo(0); - assertThat(argumentLimit.getValue()).isEqualTo(11); - assertThat(result.hasMoreResults()).isFalse(); + assertThat(argumentLimit.getValue()).isEqualTo(10); + assertThat(result.total()).isEqualTo(4); } @Test @@ -146,7 +157,7 @@ public class PermissionFinderTest { newArrayList(new GroupWithPermissionDto().setName("users").setPermission("user")) ); - GroupWithPermissionQueryResult result = finder.findGroupsWithPermission( + GroupWithPermissionQueryResult result = underTest.findGroupsWithPermission( PermissionQuery.builder().permission("user").membership(PermissionQuery.IN).build()); assertThat(result.groups()).hasSize(1); assertThat(result.hasMoreResults()).isFalse(); @@ -162,7 +173,7 @@ public class PermissionFinderTest { new GroupWithPermissionDto().setName("Other").setPermission(null) )); - GroupWithPermissionQueryResult result = finder.findGroupsWithPermission( + GroupWithPermissionQueryResult result = underTest.findGroupsWithPermission( PermissionQuery.builder() .permission("user") .pageSize(2) @@ -175,7 +186,7 @@ public class PermissionFinderTest { assertThat(groups.get(0).name()).isEqualTo("Users"); assertThat(groups.get(1).name()).isEqualTo("Reviewers"); - assertThat(finder.findGroupsWithPermission( + assertThat(underTest.findGroupsWithPermission( PermissionQuery.builder() .permission("user") .pageSize(2) @@ -193,11 +204,11 @@ public class PermissionFinderTest { new GroupWithPermissionDto().setName("Other").setPermission(null) )); - assertThat(finder.findGroupsWithPermission( + assertThat(underTest.findGroupsWithPermission( PermissionQuery.builder().permission("user").membership(PermissionQuery.IN).build()).groups()).hasSize(2); - assertThat(finder.findGroupsWithPermission( + assertThat(underTest.findGroupsWithPermission( PermissionQuery.builder().permission("user").membership(PermissionQuery.OUT).build()).groups()).hasSize(3); - assertThat(finder.findGroupsWithPermission( + assertThat(underTest.findGroupsWithPermission( PermissionQuery.builder().permission("user").membership(PermissionQuery.ANY).build()).groups()).hasSize(5); } @@ -207,7 +218,7 @@ public class PermissionFinderTest { newArrayList(new GroupWithPermissionDto().setName("users").setPermission("user")) ); - GroupWithPermissionQueryResult result = finder.findGroupsWithPermission(PermissionQuery.builder().permission("user") + GroupWithPermissionQueryResult result = underTest.findGroupsWithPermission(PermissionQuery.builder().permission("user") .pageIndex(1).membership(PermissionQuery.ANY).build()); assertThat(result.groups()).hasSize(2); GroupWithPermission first = result.groups().get(0); @@ -221,7 +232,7 @@ public class PermissionFinderTest { newArrayList(new GroupWithPermissionDto().setName("users").setPermission("user")) ); - GroupWithPermissionQueryResult result = finder.findGroupsWithPermission(PermissionQuery.builder().permission("user").search("other") + GroupWithPermissionQueryResult result = underTest.findGroupsWithPermission(PermissionQuery.builder().permission("user").search("other") .pageIndex(1).membership(PermissionQuery.ANY).build()); // Anyone group should not be added assertThat(result.groups()).hasSize(1); @@ -233,7 +244,7 @@ public class PermissionFinderTest { newArrayList(new GroupWithPermissionDto().setName("MyAnyGroup").setPermission("user")) ); - GroupWithPermissionQueryResult result = finder.findGroupsWithPermission(PermissionQuery.builder().permission("user").search("any") + GroupWithPermissionQueryResult result = underTest.findGroupsWithPermission(PermissionQuery.builder().permission("user").search("any") .pageIndex(1).membership(PermissionQuery.ANY).build()); assertThat(result.groups()).hasSize(2); } @@ -244,7 +255,7 @@ public class PermissionFinderTest { newArrayList(new GroupWithPermissionDto().setName("users").setPermission("user")) ); - GroupWithPermissionQueryResult result = finder.findGroupsWithPermission(PermissionQuery.builder().permission("user") + GroupWithPermissionQueryResult result = underTest.findGroupsWithPermission(PermissionQuery.builder().permission("user") .pageIndex(1).membership(PermissionQuery.OUT).build()); // Anyone group should not be added assertThat(result.groups()).hasSize(1); @@ -254,13 +265,15 @@ public class PermissionFinderTest { public void find_users_from_permission_template() { when(permissionTemplateDao.selectTemplateByKey(anyString())).thenReturn(new PermissionTemplateDto().setId(1L).setKee("my_template")); - when(permissionTemplateDao.selectUsers(any(PermissionQuery.class), anyLong(), anyInt(), anyInt())).thenReturn( + when(permissionTemplateDao.selectUsers(any(DbSession.class), any(PermissionQuery.class), anyLong(), anyInt(), anyInt())).thenReturn( newArrayList(new UserWithPermissionDto().setName("user1").setPermission("user")) ); - UserWithPermissionQueryResult result = finder.findUsersWithPermissionTemplate(PermissionQuery.builder().permission("user").template("my_template").build()); + when(permissionTemplateDao.countUsers(any(DbSession.class), any(PermissionQuery.class), anyLong())).thenReturn(1); + + UserWithPermissionQueryResult result = underTest.findUsersWithPermissionTemplate(PermissionQuery.builder().permission("user").template("my_template").build()); assertThat(result.users()).hasSize(1); - assertThat(result.hasMoreResults()).isFalse(); + assertThat(result.total()).isEqualTo(1); } @Test @@ -268,7 +281,7 @@ public class PermissionFinderTest { when(permissionTemplateDao.selectTemplateByKey(anyString())).thenReturn(null); try { - finder.findUsersWithPermissionTemplate(PermissionQuery.builder().permission("user").template("Unknown").build()); + underTest.findUsersWithPermissionTemplate(PermissionQuery.builder().permission("user").template("Unknown").build()); fail(); } catch (Exception e) { assertThat(e).isInstanceOf(NotFoundException.class).hasMessage("Template 'Unknown' does not exist"); @@ -283,7 +296,7 @@ public class PermissionFinderTest { newArrayList(new GroupWithPermissionDto().setName("users").setPermission("user")) ); - GroupWithPermissionQueryResult result = finder.findGroupsWithPermissionTemplate( + GroupWithPermissionQueryResult result = underTest.findGroupsWithPermissionTemplate( PermissionQuery.builder().permission("user").template("my_template").membership(PermissionQuery.OUT).build()); assertThat(result.groups()).hasSize(1); assertThat(result.hasMoreResults()).isFalse(); @@ -294,7 +307,7 @@ public class PermissionFinderTest { when(permissionTemplateDao.selectTemplateByKey(anyString())).thenReturn(null); try { - finder.findGroupsWithPermissionTemplate(PermissionQuery.builder().permission("user").template("Unknown").build()); + underTest.findGroupsWithPermissionTemplate(PermissionQuery.builder().permission("user").template("Unknown").build()); fail(); } catch (Exception e) { assertThat(e).isInstanceOf(NotFoundException.class).hasMessage("Template 'Unknown' does not exist"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/PermissionsWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/PermissionsWsModuleTest.java index 075a7e6ceaa..f85654d5c1f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/PermissionsWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/PermissionsWsModuleTest.java @@ -30,6 +30,6 @@ public class PermissionsWsModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new PermissionsWsModule().configure(container); - assertThat(container.size()).isEqualTo(7); + assertThat(container.size()).isEqualTo(8); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/UsersActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/UsersActionTest.java new file mode 100644 index 00000000000..f3bb7874ce7 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/UsersActionTest.java @@ -0,0 +1,175 @@ +/* + * 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.permission.ws; + +import com.google.common.io.Resources; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.ExpectedException; +import org.sonar.api.server.ws.WebService.Param; +import org.sonar.api.server.ws.WebService.SelectionMode; +import org.sonar.api.utils.System2; +import org.sonar.core.permission.GlobalPermissions; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.user.UserDto; +import org.sonar.db.user.UserRoleDto; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.UnauthorizedException; +import org.sonar.server.permission.PermissionFinder; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.WsActionTester; +import org.sonar.test.DbTests; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.test.JsonAssert.assertJson; + +@Category(DbTests.class) +public class UsersActionTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + DbClient dbClient = db.getDbClient(); + DbSession dbSession = db.getSession(); + WsActionTester ws; + PermissionFinder permissionFinder; + + UsersAction underTest; + + @Before + public void setUp() { + permissionFinder = new PermissionFinder(dbClient); + userSession.login("login").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN); + underTest = new UsersAction(userSession, permissionFinder); + ws = new WsActionTester(underTest); + + UserDto user1 = dbClient.userDao().insert(dbSession, new UserDto() + .setActive(true) + .setLogin("login-1") + .setName("name-1")); + UserDto user2 = dbClient.userDao().insert(dbSession, new UserDto() + .setActive(true) + .setLogin("login-2") + .setName("name-2")); + UserDto user3 = dbClient.userDao().insert(dbSession, new UserDto() + .setActive(true) + .setLogin("login-3") + .setName("name-3")); + dbClient.roleDao().insertUserRole(dbSession, new UserRoleDto() + .setRole(GlobalPermissions.SCAN_EXECUTION) + .setUserId(user1.getId())); + dbClient.roleDao().insertUserRole(dbSession, new UserRoleDto() + .setRole(GlobalPermissions.SCAN_EXECUTION) + .setUserId(user2.getId())); + dbClient.roleDao().insertUserRole(dbSession, new UserRoleDto() + .setRole(GlobalPermissions.SYSTEM_ADMIN) + .setUserId(user3.getId())); + dbSession.commit(); + } + + @Test + public void search_for_users_with_one_permission() { + String result = ws.newRequest().setParam("permission", "scan").execute().getInput(); + + assertJson(result).isSimilarTo(Resources.getResource(getClass(), "UsersActionTest/users.json")); + } + + @Test + public void search_for_users_with_response_example() { + db.truncateTables(); + UserDto user1 = dbClient.userDao().insert(dbSession, new UserDto() + .setActive(true) + .setLogin("admin") + .setName("Administrator")); + UserDto user2 = dbClient.userDao().insert(dbSession, new UserDto() + .setActive(true) + .setLogin("george.orwell") + .setName("George Orwell")); + dbClient.roleDao().insertUserRole(dbSession, new UserRoleDto() + .setRole(GlobalPermissions.SCAN_EXECUTION) + .setUserId(user1.getId())); + dbClient.roleDao().insertUserRole(dbSession, new UserRoleDto() + .setRole(GlobalPermissions.SCAN_EXECUTION) + .setUserId(user2.getId())); + dbSession.commit(); + + String result = ws.newRequest().setParam("permission", "scan").execute().getInput(); + + assertJson(result).isSimilarTo(Resources.getResource(getClass(), "users-example.json")); + } + + @Test + public void search_for_users_with_query_as_a_parameter() { + String result = ws.newRequest() + .setParam("permission", "scan") + .setParam(Param.TEXT_QUERY, "ame-1") + .execute().getInput(); + + assertThat(result).contains("login-1") + .doesNotContain("login-2") + .doesNotContain("login-3"); + + } + + @Test + public void search_for_users_with_select_as_a_parameter() { + String result = ws.newRequest() + .setParam("permission", "scan") + .setParam(Param.SELECTED, SelectionMode.ALL.value()) + .execute().getInput(); + + assertThat(result).contains("login-1", "login-2", "login-3"); + } + + @Test + public void fail_if_permission_parameter_is_not_filled() { + expectedException.expect(IllegalArgumentException.class); + + ws.newRequest().execute(); + } + + @Test + public void fail_if_insufficient_privileges() { + expectedException.expect(ForbiddenException.class); + userSession.login("login"); + + ws.newRequest() + .setParam("permission", GlobalPermissions.SYSTEM_ADMIN) + .execute(); + } + + @Test + public void fail_if_not_logged_in() { + expectedException.expect(UnauthorizedException.class); + userSession.anonymous(); + + ws.newRequest() + .setParam("permission", GlobalPermissions.SYSTEM_ADMIN) + .execute(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/ws/TestResponse.java b/server/sonar-server/src/test/java/org/sonar/server/ws/TestResponse.java index e76480462fc..c0f59742750 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ws/TestResponse.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ws/TestResponse.java @@ -42,5 +42,4 @@ public class TestResponse { public String getMediaType() { return dumbResponse.stream().mediaType(); } - } diff --git a/server/sonar-server/src/test/resources/org/sonar/server/permission/ws/UsersActionTest/users.json b/server/sonar-server/src/test/resources/org/sonar/server/permission/ws/UsersActionTest/users.json new file mode 100644 index 00000000000..a3ecb8c0729 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/permission/ws/UsersActionTest/users.json @@ -0,0 +1,19 @@ +{ + "users": [ + { + "login": "login-1", + "name": "name-1", + "selected": true + }, + { + "login": "login-2", + "name": "name-2", + "selected": true + } + ], + "paging": { + "pageSize": 100, + "total": 2, + "pages": 1 + } +} diff --git a/sonar-db/src/main/java/org/sonar/db/permission/PermissionDao.java b/sonar-db/src/main/java/org/sonar/db/permission/PermissionDao.java index e18bdbbcb57..ae1ae1b65da 100644 --- a/sonar-db/src/main/java/org/sonar/db/permission/PermissionDao.java +++ b/sonar-db/src/main/java/org/sonar/db/permission/PermissionDao.java @@ -20,7 +20,6 @@ package org.sonar.db.permission; -import com.google.common.annotations.VisibleForTesting; import java.util.List; import java.util.Map; import javax.annotation.Nullable; @@ -28,6 +27,7 @@ import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.session.SqlSession; import org.sonar.api.security.DefaultGroups; import org.sonar.db.Dao; +import org.sonar.db.DbSession; import org.sonar.db.MyBatis; import static com.google.common.collect.Maps.newHashMap; @@ -46,27 +46,29 @@ public class PermissionDao implements Dao { /** * @return a paginated list of users. */ - public List selectUsers(PermissionQuery query, @Nullable Long componentId, int offset, int limit) { - SqlSession session = myBatis.openSession(false); - try { - Map params = newHashMap(); - params.put(QUERY_PARAMETER, query); - params.put(COMPONENT_ID_PARAMETER, componentId); + public List selectUsers(DbSession session, PermissionQuery query, @Nullable Long componentId, int offset, int limit) { + Map params = usersParameters(query, componentId); - return mapper(session).selectUsers(params, new RowBounds(offset, limit)); - } finally { - MyBatis.closeQuietly(session); - } + return mapper(session).selectUsers(params, new RowBounds(offset, limit)); + } + + public int countUsers(DbSession session, PermissionQuery query, @Nullable Long componentId) { + Map params = usersParameters(query, componentId); + + return mapper(session).countUsers(params); } - @VisibleForTesting - List selectUsers(PermissionQuery query, @Nullable Long componentId) { - return selectUsers(query, componentId, 0, Integer.MAX_VALUE); + private static Map usersParameters(PermissionQuery query, @Nullable Long componentId) { + Map params = newHashMap(); + params.put(QUERY_PARAMETER, query); + params.put(COMPONENT_ID_PARAMETER, componentId); + return params; } /** * 'Anyone' group is not returned when it has not the asked permission. * Membership parameter from query is not taking into account in order to deal more easily with the 'Anyone' group + * * @return a non paginated list of groups. */ public List selectGroups(PermissionQuery query, @Nullable Long componentId) { @@ -86,5 +88,4 @@ public class PermissionDao implements Dao { private PermissionMapper mapper(SqlSession session) { return session.getMapper(PermissionMapper.class); } - } diff --git a/sonar-db/src/main/java/org/sonar/db/permission/PermissionMapper.java b/sonar-db/src/main/java/org/sonar/db/permission/PermissionMapper.java index 08c7b3e07c0..0bf57813c53 100644 --- a/sonar-db/src/main/java/org/sonar/db/permission/PermissionMapper.java +++ b/sonar-db/src/main/java/org/sonar/db/permission/PermissionMapper.java @@ -29,4 +29,6 @@ public interface PermissionMapper { List selectUsers(Map parameters, RowBounds rowBounds); List selectGroups(Map parameters); + + int countUsers(Map parameters); } diff --git a/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateDao.java b/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateDao.java index b595e1609ad..30945f54e72 100644 --- a/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateDao.java +++ b/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateDao.java @@ -54,17 +54,31 @@ public class PermissionTemplateDao implements Dao { * @return a paginated list of users. */ public List selectUsers(PermissionQuery query, Long templateId, int offset, int limit) { - SqlSession session = myBatis.openSession(false); + DbSession session = myBatis.openSession(false); try { - Map params = newHashMap(); - params.put(QUERY_PARAMETER, query); - params.put(TEMPLATE_ID_PARAMETER, templateId); - return mapper(session).selectUsers(params, new RowBounds(offset, limit)); + return selectUsers(session, query, templateId, offset, limit); } finally { MyBatis.closeQuietly(session); } } + /** + * @return a paginated list of users. + */ + public List selectUsers(DbSession session, PermissionQuery query, Long templateId, int offset, int limit) { + Map params = newHashMap(); + params.put(QUERY_PARAMETER, query); + params.put(TEMPLATE_ID_PARAMETER, templateId); + return mapper(session).selectUsers(params, new RowBounds(offset, limit)); + } + + public int countUsers(DbSession session, PermissionQuery query, Long templateId) { + Map params = newHashMap(); + params.put(QUERY_PARAMETER, query); + params.put(TEMPLATE_ID_PARAMETER, templateId); + return mapper(session).countUsers(params); + } + @VisibleForTesting List selectUsers(PermissionQuery query, Long templateId) { return selectUsers(query, templateId, 0, Integer.MAX_VALUE); diff --git a/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateMapper.java b/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateMapper.java index 1277462b14d..be35219518e 100644 --- a/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateMapper.java +++ b/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateMapper.java @@ -58,4 +58,6 @@ public interface PermissionTemplateMapper { List selectGroups(Map params); List selectUsers(Map params, RowBounds rowBounds); + + int countUsers(Map params); } diff --git a/sonar-db/src/main/resources/org/sonar/db/permission/PermissionMapper.xml b/sonar-db/src/main/resources/org/sonar/db/permission/PermissionMapper.xml index 79a705a44dc..a2e01badf9b 100644 --- a/sonar-db/src/main/resources/org/sonar/db/permission/PermissionMapper.xml +++ b/sonar-db/src/main/resources/org/sonar/db/permission/PermissionMapper.xml @@ -5,6 +5,16 @@ + + + + FROM users u LEFT JOIN user_roles user_role ON user_role.user_id=u.id AND user_role.role=#{query.permission} @@ -28,8 +38,7 @@ AND (UPPER(u.name) LIKE #{query.searchSql} ESCAPE '/') - ORDER BY u.name - + SELECT u.login as login, u.name as name, ptu.permission_reference as permission + + ORDER BY u.name + + + + + FROM users u LEFT JOIN perm_templates_users ptu ON ptu.user_id=u.id AND ptu.permission_reference=#{query.permission} @@ -86,8 +96,7 @@ AND (UPPER(u.name) LIKE #{query.searchSql} ESCAPE '/') - ORDER BY u.name - +