From f8559bcc4887f9fc2a6592bb8414c78a55ba2a5d Mon Sep 17 00:00:00 2001 From: Teryk Bellahsene Date: Tue, 1 Sep 2015 08:22:42 +0200 Subject: [PATCH] SONAR-6494 WS permissions/apply_template apply a permission template to a project --- .../permission/ws/ApplyTemplateAction.java | 82 +++++ .../ws/PermissionDependenciesFinder.java | 6 +- .../permission/ws/PermissionsWsModule.java | 1 + .../server/permission/ws/WsProjectRef.java | 8 +- .../ws/ApplyTemplateActionTest.java | 302 ++++++++++++++++++ .../ws/PermissionsWsModuleTest.java | 2 +- 6 files changed, 393 insertions(+), 8 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/permission/ws/ApplyTemplateAction.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/permission/ws/ApplyTemplateActionTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/ws/ApplyTemplateAction.java b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/ApplyTemplateAction.java new file mode 100644 index 000000000000..d449c7d416ea --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/ApplyTemplateAction.java @@ -0,0 +1,82 @@ +/* + * 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 org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.permission.PermissionTemplateDto; +import org.sonar.server.permission.ApplyPermissionTemplateQuery; +import org.sonar.server.permission.PermissionService; + +import static java.util.Collections.singletonList; +import static org.sonar.server.permission.ws.Parameters.PARAM_TEMPLATE_ID_EXPLICIT; +import static org.sonar.server.permission.ws.Parameters.createProjectParameter; +import static org.sonar.server.permission.ws.Parameters.createExplicitTemplateId; + +public class ApplyTemplateAction implements PermissionsWsAction { + private final DbClient dbClient; + private final PermissionService permissionService; + private final PermissionDependenciesFinder finder; + + public ApplyTemplateAction(DbClient dbClient, PermissionService permissionService, PermissionDependenciesFinder finder) { + this.dbClient = dbClient; + this.permissionService = permissionService; + this.finder = finder; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("apply_template") + .setDescription("Apply a permission template to one or several projects.
" + + "The project id or project key must be provided.
" + + "It requires administration permissions to access.") + .setPost(true) + .setSince("5.2") + .setHandler(this); + + createExplicitTemplateId(action); + createProjectParameter(action); + } + + @Override + public void handle(Request wsRequest, Response wsResponse) throws Exception { + String templateUuid = wsRequest.mandatoryParam(PARAM_TEMPLATE_ID_EXPLICIT); + + DbSession dbSession = dbClient.openSession(false); + try { + PermissionTemplateDto template = finder.getTemplate(dbSession, templateUuid); + ComponentDto project = finder.getProject(dbSession, wsRequest); + + ApplyPermissionTemplateQuery query = ApplyPermissionTemplateQuery.create( + template.getUuid(), + singletonList(project.key())); + permissionService.applyPermissionTemplate(query); + } finally { + dbClient.closeSession(dbSession); + } + + wsResponse.noContent(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionDependenciesFinder.java b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionDependenciesFinder.java index 9825d1f34ad0..0fee0e312580 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionDependenciesFinder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionDependenciesFinder.java @@ -35,7 +35,6 @@ import static org.sonar.api.security.DefaultGroups.ANYONE; import static org.sonar.api.security.DefaultGroups.isAnyone; import static org.sonar.server.ws.WsUtils.checkFound; -import static org.sonar.server.ws.WsUtils.checkRequest; public class PermissionDependenciesFinder { private final DbClient dbClient; @@ -59,10 +58,9 @@ Optional searchProject(DbSession dbSession, PermissionRequest requ } public ComponentDto getProject(DbSession dbSession, Request wsRequest) { - Optional projectRef = WsProjectRef.optionalFromRequest(wsRequest); - checkRequest(projectRef.isPresent(), "The project id or the project key must be provided."); + WsProjectRef projectRef = WsProjectRef.fromRequest(wsRequest); - return componentFinder.getProjectByUuidOrKey(dbSession, projectRef.get().uuid(), projectRef.get().key()); + return componentFinder.getProjectByUuidOrKey(dbSession, projectRef.uuid(), projectRef.key()); } String getGroupName(DbSession dbSession, PermissionRequest request) { 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 6769088cde54..2c139b1545f6 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 @@ -43,6 +43,7 @@ protected void configureModule() { CreateTemplateAction.class, UpdateTemplateAction.class, DeleteTemplateAction.class, + ApplyTemplateAction.class, // utility classes PermissionChangeBuilder.class, SearchProjectPermissionsDataLoader.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/ws/WsProjectRef.java b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/WsProjectRef.java index f83c6d210cac..c51c87b5c8b6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/ws/WsProjectRef.java +++ b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/WsProjectRef.java @@ -45,7 +45,7 @@ private WsProjectRef(Request wsRequest) { } static Optional optionalFromRequest(Request wsRequest) { - if (hasNoProjectParam(wsRequest)) { + if (!hasProjectParam(wsRequest)) { return Optional.absent(); } @@ -53,6 +53,8 @@ static Optional optionalFromRequest(Request wsRequest) { } static WsProjectRef fromRequest(Request wsRequest) { + checkRequest(hasProjectParam(wsRequest), "Project id or project key must be provided, not both."); + return new WsProjectRef(wsRequest); } @@ -66,7 +68,7 @@ String key() { return this.key; } - private static boolean hasNoProjectParam(Request wsRequest) { - return !wsRequest.hasParam(PARAM_PROJECT_ID) && !wsRequest.hasParam(PARAM_PROJECT_KEY); + private static boolean hasProjectParam(Request wsRequest) { + return wsRequest.hasParam(PARAM_PROJECT_ID) || wsRequest.hasParam(PARAM_PROJECT_KEY); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/ApplyTemplateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/ApplyTemplateActionTest.java new file mode 100644 index 000000000000..33770ed5b720 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/ApplyTemplateActionTest.java @@ -0,0 +1,302 @@ +/* + * 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.base.Predicate; +import com.google.common.collect.FluentIterable; +import java.util.List; +import javax.annotation.Nullable; +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.config.Settings; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +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.component.ComponentDto; +import org.sonar.db.permission.GroupWithPermissionDto; +import org.sonar.db.permission.PermissionQuery; +import org.sonar.db.permission.PermissionRepository; +import org.sonar.db.permission.PermissionTemplateDto; +import org.sonar.db.permission.UserWithPermissionDto; +import org.sonar.db.user.GroupDto; +import org.sonar.db.user.GroupRoleDto; +import org.sonar.db.user.UserDto; +import org.sonar.db.user.UserRoleDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.exceptions.BadRequestException; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.exceptions.UnauthorizedException; +import org.sonar.server.issue.index.IssueAuthorizationIndexer; +import org.sonar.server.permission.PermissionFinder; +import org.sonar.server.permission.PermissionService; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.TestRequest; +import org.sonar.server.ws.TestResponse; +import org.sonar.server.ws.WsActionTester; +import org.sonar.test.DbTests; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.sonar.db.component.ComponentTesting.newProjectDto; +import static org.sonar.db.permission.PermissionTemplateTesting.newPermissionTemplateDto; +import static org.sonar.db.user.GroupMembershipQuery.IN; +import static org.sonar.db.user.GroupTesting.newGroupDto; +import static org.sonar.db.user.UserTesting.newUserDto; +import static org.sonar.server.permission.ws.Parameters.PARAM_PROJECT_ID; +import static org.sonar.server.permission.ws.Parameters.PARAM_PROJECT_KEY; +import static org.sonar.server.permission.ws.Parameters.PARAM_TEMPLATE_ID_EXPLICIT; + +@Category(DbTests.class) +public class ApplyTemplateActionTest { + + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + WsActionTester ws; + DbClient dbClient; + DbSession dbSession; + + UserDto user1; + UserDto user2; + GroupDto group1; + GroupDto group2; + ComponentDto project; + PermissionTemplateDto template1; + PermissionTemplateDto template2; + IssueAuthorizationIndexer issueAuthorizationIndexer = mock(IssueAuthorizationIndexer.class); + + @Before + public void setUp() { + userSession.login("login").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN); + dbClient = db.getDbClient(); + dbSession = db.getSession(); + + PermissionRepository repository = new PermissionRepository(dbClient, new Settings()); + PermissionFinder permissionFinder = new PermissionFinder(dbClient); + ComponentFinder componentFinder = new ComponentFinder(dbClient); + PermissionService permissionService = new PermissionService(dbClient, repository, permissionFinder, issueAuthorizationIndexer, userSession, componentFinder); + PermissionDependenciesFinder permissionDependenciesFinder = new PermissionDependenciesFinder(dbClient, componentFinder); + + ApplyTemplateAction underTest = new ApplyTemplateAction(dbClient, permissionService, permissionDependenciesFinder); + ws = new WsActionTester(underTest); + + user1 = insertUser(newUserDto().setLogin("user-login-1")); + user2 = insertUser(newUserDto().setLogin("user-login-2")); + group1 = insertGroup(newGroupDto().setName("group-name-1")); + group2 = insertGroup(newGroupDto().setName("group-name-2")); + + // template 1 + template1 = insertTemplate(newPermissionTemplateDto().setUuid("permission-template-uuid-1")); + addUserToTemplate(user1, template1, UserRole.CODEVIEWER); + addUserToTemplate(user2, template1, UserRole.ISSUE_ADMIN); + addGroupToTemplate(group1, template1, UserRole.ADMIN); + addGroupToTemplate(group2, template1, UserRole.USER); + // template 2 + template2 = insertTemplate(newPermissionTemplateDto().setUuid("permission-template-uuid-2")); + addUserToTemplate(user1, template2, UserRole.USER); + addUserToTemplate(user2, template2, UserRole.USER); + addGroupToTemplate(group1, template2, UserRole.USER); + addGroupToTemplate(group2, template2, UserRole.USER); + + project = insertProject(newProjectDto("project-uuid-1")); + addUserPermissionToProject(user1, project, UserRole.ADMIN); + addUserPermissionToProject(user2, project, UserRole.ADMIN); + addGroupPermissionToProject(group1, project, UserRole.ADMIN); + addGroupPermissionToProject(group2, project, UserRole.ADMIN); + + commit(); + } + + @Test + public void apply_template_with_project_uuid() { + assertThat(selectProjectPermissionGroups(project, UserRole.ADMIN)).hasSize(2); + assertThat(selectProjectPermissionUsers(project, UserRole.ADMIN)).hasSize(2); + + newRequest(template1.getUuid(), project.uuid(), null); + + assertTemplate1AppliedToProject(); + verify(issueAuthorizationIndexer).index(); + } + + @Test + public void apply_template_with_project_key() { + newRequest(template1.getUuid(), null, project.key()); + + assertTemplate1AppliedToProject(); + } + + @Test + public void fail_when_unknown_template() { + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("Permission template with id 'unknown-template-uuid' is not found"); + + newRequest("unknown-template-uuid", project.uuid(), null); + } + + @Test + public void fail_when_unknown_project_uuid() { + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("Project id 'unknown-project-uuid' not found"); + + newRequest(template1.getUuid(), "unknown-project-uuid", null); + } + + @Test + public void fail_when_unknown_project_key() { + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("Project key 'unknown-project-key' not found"); + + newRequest(template1.getUuid(), null, "unknown-project-key"); + } + + @Test + public void fail_when_template_is_not_provided() { + expectedException.expect(IllegalArgumentException.class); + + newRequest(null, project.uuid(), null); + } + + @Test + public void fail_when_project_uuid_and_key_not_provided() { + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("Project id or project key must be provided, not both."); + + newRequest(template1.getUuid(), null, null); + } + + @Test + public void fail_when_anonymous() { + expectedException.expect(UnauthorizedException.class); + userSession.anonymous(); + + newRequest(template1.getUuid(), project.uuid(), null); + } + + @Test + public void fail_when_insufficient_privileges() { + expectedException.expect(ForbiddenException.class); + userSession.login().setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); + + newRequest(template1.getUuid(), project.uuid(), null); + } + + private void assertTemplate1AppliedToProject() { + assertThat(selectProjectPermissionGroups(project, UserRole.ADMIN)).extracting("name").containsExactly(group1.getName()); + assertThat(selectProjectPermissionGroups(project, UserRole.USER)).extracting("name").containsExactly(group2.getName()); + assertThat(selectProjectPermissionUsers(project, UserRole.ADMIN)).isEmpty(); + assertThat(selectProjectPermissionUsers(project, UserRole.CODEVIEWER)).extracting("login").containsExactly(user1.getLogin()); + assertThat(selectProjectPermissionUsers(project, UserRole.ISSUE_ADMIN)).extracting("login").containsExactly(user2.getLogin()); + } + + private TestResponse newRequest(@Nullable String templateUuid, @Nullable String projectUuid, @Nullable String projectKey) { + TestRequest request = ws.newRequest(); + if (templateUuid != null) { + request.setParam(PARAM_TEMPLATE_ID_EXPLICIT, templateUuid); + } + if (projectUuid != null) { + request.setParam(PARAM_PROJECT_ID, projectUuid); + } + if (projectKey != null) { + request.setParam(PARAM_PROJECT_KEY, projectKey); + } + + TestResponse result = request.execute(); + commit(); + + return result; + } + + private ComponentDto insertProject(ComponentDto project) { + dbClient.componentDao().insert(dbSession, project); + return dbClient.componentDao().selectOrFailByUuid(dbSession, project.uuid()); + } + + private PermissionTemplateDto insertTemplate(PermissionTemplateDto template) { + return dbClient.permissionTemplateDao().insert(dbSession, template); + } + + private UserDto insertUser(UserDto userDto) { + return dbClient.userDao().insert(dbSession, userDto.setActive(true)); + } + + private GroupDto insertGroup(GroupDto group) { + return dbClient.groupDao().insert(dbSession, group); + } + + private void addUserToTemplate(UserDto user, PermissionTemplateDto permissionTemplate, String permission) { + dbClient.permissionTemplateDao().insertUserPermission(dbSession, permissionTemplate.getId(), user.getId(), permission); + } + + private void addGroupToTemplate(GroupDto group, PermissionTemplateDto permissionTemplate, String permission) { + dbClient.permissionTemplateDao().insertGroupPermission(dbSession, permissionTemplate.getId(), group.getId(), permission); + } + + private void addUserPermissionToProject(UserDto user, ComponentDto project, String permission) { + dbClient.roleDao().insertUserRole(dbSession, new UserRoleDto() + .setRole(permission) + .setUserId(user.getId()) + .setResourceId(project.getId())); + } + + private void addGroupPermissionToProject(GroupDto group, ComponentDto project, String permission) { + dbClient.roleDao().insertGroupRole(dbSession, new GroupRoleDto() + .setRole(permission) + .setResourceId(project.getId()) + .setGroupId(group.getId())); + } + + private List selectProjectPermissionGroups(ComponentDto project, String permission) { + return FluentIterable.from(dbClient.permissionDao().selectGroups(dbSession, query(permission), project.getId())) + .filter(new PermissionNotNull()) + .toList(); + } + + private List selectProjectPermissionUsers(ComponentDto project, String permission) { + return dbClient.permissionDao().selectUsers(dbSession, query(permission), project.getId(), 0, Integer.MAX_VALUE); + } + + private void commit() { + dbSession.commit(); + } + + private static PermissionQuery query(String permission) { + return PermissionQuery.builder().membership(IN).permission(permission).build(); + } + + private static class PermissionNotNull implements Predicate { + @Override + public boolean apply(@Nullable GroupWithPermissionDto input) { + return input.getPermission() != null; + } + } +} 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 c8c59d230901..37575dd01c18 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(22); + assertThat(container.size()).isEqualTo(23); } }