From bb85616bc47bdc6ea6e067c61adc6d99c55cf5af Mon Sep 17 00:00:00 2001 From: Teryk Bellahsene Date: Thu, 20 Aug 2015 17:54:57 +0200 Subject: [PATCH] SONAR-6500 WS permissions/add_user_to_template add a user to a permission template --- .../ws/AddUserToTemplateAction.java | 115 ++++++++++ .../server/permission/ws/Parameters.java | 9 + .../ws/PermissionDependenciesFinder.java | 12 +- .../permission/ws/PermissionsWsModule.java | 3 +- .../ws/AddUserToTemplateActionTest.java | 208 ++++++++++++++++++ .../ws/PermissionsWsModuleTest.java | 2 +- .../permission/ws/RemoveGroupActionTest.java | 5 +- .../db/permission/PermissionTemplateDao.java | 23 +- .../permission/PermissionTemplateTesting.java | 37 ++++ 9 files changed, 396 insertions(+), 18 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/permission/ws/AddUserToTemplateAction.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddUserToTemplateActionTest.java create mode 100644 sonar-db/src/test/java/org/sonar/db/permission/PermissionTemplateTesting.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/ws/AddUserToTemplateAction.java b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/AddUserToTemplateAction.java new file mode 100644 index 00000000000..78b5e7ec988 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/AddUserToTemplateAction.java @@ -0,0 +1,115 @@ +/* + * 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 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.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.permission.PermissionQuery; +import org.sonar.db.permission.PermissionTemplateDto; +import org.sonar.db.permission.UserWithPermissionDto; +import org.sonar.db.user.UserDto; +import org.sonar.server.user.UserSession; + +import static com.google.common.collect.FluentIterable.from; +import static org.sonar.db.user.GroupMembershipQuery.IN; +import static org.sonar.server.permission.PermissionPrivilegeChecker.checkGlobalAdminUser; +import static org.sonar.server.permission.PermissionValueValidator.validateProjectPermission; +import static org.sonar.server.permission.ws.Parameters.PARAM_PERMISSION; +import static org.sonar.server.permission.ws.Parameters.PARAM_TEMPLATE_KEY; +import static org.sonar.server.permission.ws.Parameters.PARAM_USER_LOGIN; +import static org.sonar.server.permission.ws.Parameters.createPermissionParameter; +import static org.sonar.server.permission.ws.Parameters.createTemplateKeyParameter; +import static org.sonar.server.permission.ws.Parameters.createUserLoginParameter; + +public class AddUserToTemplateAction implements PermissionsWsAction { + private final DbClient dbClient; + private final PermissionDependenciesFinder dependenciesFinder; + private final UserSession userSession; + + public AddUserToTemplateAction(DbClient dbClient, PermissionDependenciesFinder dependenciesFinder, UserSession userSession) { + this.dbClient = dbClient; + this.dependenciesFinder = dependenciesFinder; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context + .createAction("add_user_to_template") + .setPost(true) + .setSince("5.2") + .setDescription("Add a user to a permission template.
" + + "It requires administration permissions to access.") + .setHandler(this); + + createTemplateKeyParameter(action); + createPermissionParameter(action); + createUserLoginParameter(action); + } + + @Override + public void handle(Request wsRequest, Response wsResponse) throws Exception { + checkGlobalAdminUser(userSession); + + String templateKey = wsRequest.mandatoryParam(PARAM_TEMPLATE_KEY); + String permission = wsRequest.mandatoryParam(PARAM_PERMISSION); + final String userLogin = wsRequest.mandatoryParam(PARAM_USER_LOGIN); + + DbSession dbSession = dbClient.openSession(false); + try { + validateProjectPermission(permission); + PermissionTemplateDto template = dependenciesFinder.getTemplate(templateKey); + UserDto user = dependenciesFinder.getUser(dbSession, userLogin); + + if (!isUserAlreadyAdded(dbSession, template.getId(), userLogin, permission)) { + dbClient.permissionTemplateDao().insertUserPermission(dbSession, template.getId(), user.getId(), permission); + } + } finally { + dbClient.closeSession(dbSession); + } + + wsResponse.noContent(); + } + + private boolean isUserAlreadyAdded(DbSession dbSession, long templateId, String userLogin, String permission) { + PermissionQuery permissionQuery = PermissionQuery.builder().permission(permission).membership(IN).build(); + List usersWithPermission = dbClient.permissionTemplateDao().selectUsers(dbSession, permissionQuery, templateId, 0, Integer.MAX_VALUE); + return from(usersWithPermission).anyMatch(new HasUserPredicate(userLogin)); + } + + private static class HasUserPredicate implements Predicate { + private final String userLogin; + + public HasUserPredicate(String userLogin) { + this.userLogin = userLogin; + } + + @Override + public boolean apply(UserWithPermissionDto userWithPermission) { + return userLogin.equals(userWithPermission.getLogin()); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/ws/Parameters.java b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/Parameters.java index 6938a276ea6..79eb9c1333a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/ws/Parameters.java +++ b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/Parameters.java @@ -32,6 +32,8 @@ class Parameters { static final String PARAM_PROJECT_UUID = "projectId"; static final String PARAM_PROJECT_KEY = "projectKey"; static final String PARAM_USER_LOGIN = "login"; + static final String PARAM_TEMPLATE_KEY = "templateKey"; + private static final String PERMISSION_PARAM_DESCRIPTION = String.format("Permission" + "
    " + "
  • Possible values for global permissions: %s
  • " + @@ -80,4 +82,11 @@ class Parameters { .setDescription("User login") .setExampleValue("g.hopper"); } + + static void createTemplateKeyParameter(NewAction action) { + action.createParam(PARAM_TEMPLATE_KEY) + .setRequired(true) + .setDescription("Template key") + .setExampleValue("developer_template_20150820_170218"); + } } 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 6125c7cd329..80db297ef98 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 @@ -29,7 +29,7 @@ import org.sonar.db.permission.PermissionTemplateDto; import org.sonar.db.user.GroupDto; import org.sonar.db.user.UserDto; import org.sonar.server.component.ComponentFinder; -import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.exceptions.BadRequestException; import static com.google.common.base.Preconditions.checkNotNull; import static org.sonar.api.security.DefaultGroups.ANYONE; @@ -66,7 +66,7 @@ public class PermissionDependenciesFinder { if (groupId != null) { group = dbClient.groupDao().selectById(dbSession, groupId); if (group == null) { - throw new NotFoundException(String.format("Group with id '%d' is not found", groupId)); + throw new BadRequestException(String.format("Group with id '%d' is not found", groupId)); } } @@ -74,7 +74,7 @@ public class PermissionDependenciesFinder { if (groupName != null) { group = dbClient.groupDao().selectByName(dbSession, groupName); if (group == null) { - throw new NotFoundException(String.format("Group with name '%s' is not found", groupName)); + throw new BadRequestException(String.format("Group with name '%s' is not found", groupName)); } } @@ -89,7 +89,7 @@ public class PermissionDependenciesFinder { GroupDto group = dbClient.groupDao().selectByName(dbSession, groupName); if (group == null) { - throw new NotFoundException(String.format("Group with name '%s' is not found", groupName)); + throw new BadRequestException(String.format("Group with name '%s' is not found", groupName)); } return group.getId(); } @@ -97,7 +97,7 @@ public class PermissionDependenciesFinder { UserDto getUser(DbSession dbSession, String userLogin) { UserDto user = dbClient.userDao().selectActiveUserByLogin(dbSession, userLogin); if (user == null) { - throw new NotFoundException(String.format("User with login '%s' is not found'", userLogin)); + throw new BadRequestException(String.format("User with login '%s' is not found'", userLogin)); } return user; } @@ -105,7 +105,7 @@ public class PermissionDependenciesFinder { PermissionTemplateDto getTemplate(String templateKey) { PermissionTemplateDto permissionTemplate = dbClient.permissionTemplateDao().selectTemplateByKey(templateKey); if (permissionTemplate == null) { - throw new NotFoundException(String.format("Template with key '%s' is not found", templateKey)); + throw new BadRequestException(String.format("Permission template with key '%s' is not found", templateKey)); } return permissionTemplate; } 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 cbc4905d635..0d334450b38 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 @@ -37,6 +37,7 @@ public class PermissionsWsModule extends Module { PermissionChangeBuilder.class, SearchProjectPermissionsAction.class, SearchProjectPermissionsDataLoader.class, - PermissionDependenciesFinder.class); + PermissionDependenciesFinder.class, + AddUserToTemplateAction.class); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddUserToTemplateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddUserToTemplateActionTest.java new file mode 100644 index 00000000000..957fdcf2691 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddUserToTemplateActionTest.java @@ -0,0 +1,208 @@ +/* + * 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.Function; +import java.util.List; +import javax.annotation.Nonnull; +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.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.permission.PermissionQuery; +import org.sonar.db.permission.PermissionTemplateDto; +import org.sonar.db.permission.UserWithPermissionDto; +import org.sonar.db.user.UserDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.exceptions.BadRequestException; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.UnauthorizedException; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.TestRequest; +import org.sonar.server.ws.WsActionTester; +import org.sonar.test.DbTests; + +import static com.google.common.collect.FluentIterable.from; +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.api.web.UserRole.CODEVIEWER; +import static org.sonar.api.web.UserRole.ISSUE_ADMIN; +import static org.sonar.db.permission.PermissionTemplateTesting.newPermissionTemplateDto; +import static org.sonar.db.user.GroupMembershipQuery.IN; +import static org.sonar.db.user.UserTesting.newUserDto; + +@Category(DbTests.class) +public class AddUserToTemplateActionTest { + + private static final String USER_LOGIN = "user-login"; + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + WsActionTester ws; + DbClient dbClient; + DbSession dbSession; + UserDto user; + PermissionTemplateDto permissionTemplate; + + @Before + public void setUp() { + dbClient = db.getDbClient(); + dbSession = db.getSession(); + userSession.login().setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN); + + PermissionDependenciesFinder dependenciesFinder = new PermissionDependenciesFinder(dbClient, new ComponentFinder(dbClient)); + ws = new WsActionTester(new AddUserToTemplateAction(dbClient, dependenciesFinder, userSession)); + + user = insertUser(newUserDto().setLogin(USER_LOGIN)); + permissionTemplate = insertPermissionTemplate(newPermissionTemplateDto()); + commit(); + } + + @Test + public void add_user_to_template() { + newRequest(USER_LOGIN, permissionTemplate.getKee(), CODEVIEWER); + + assertThat(getLoginsInTemplateAndPermission(permissionTemplate.getId(), CODEVIEWER)).containsExactly(USER_LOGIN); + } + + @Test + public void does_not_add_a_user_twice() { + newRequest(USER_LOGIN, permissionTemplate.getKee(), ISSUE_ADMIN); + newRequest(USER_LOGIN, permissionTemplate.getKee(), ISSUE_ADMIN); + + assertThat(getLoginsInTemplateAndPermission(permissionTemplate.getId(), ISSUE_ADMIN)).containsExactly(USER_LOGIN); + } + + @Test + public void fail_if_not_a_project_permission() { + expectedException.expect(BadRequestException.class); + + newRequest(USER_LOGIN, permissionTemplate.getKee(), GlobalPermissions.PREVIEW_EXECUTION); + } + + @Test + public void fail_if_insufficient_privileges() { + expectedException.expect(ForbiddenException.class); + userSession.setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN); + + newRequest(USER_LOGIN, permissionTemplate.getKee(), CODEVIEWER); + } + + @Test + public void fail_if_not_logged_in() { + expectedException.expect(UnauthorizedException.class); + userSession.anonymous(); + + newRequest(USER_LOGIN, permissionTemplate.getKee(), CODEVIEWER); + } + + @Test + public void fail_if_user_missing() { + expectedException.expect(IllegalArgumentException.class); + + newRequest(null, permissionTemplate.getKee(), CODEVIEWER); + } + + @Test + public void fail_if_permission_missing() { + expectedException.expect(IllegalArgumentException.class); + + newRequest(USER_LOGIN, permissionTemplate.getKee(), null); + } + + @Test + public void fail_if_template_key_missing() { + expectedException.expect(IllegalArgumentException.class); + + newRequest(USER_LOGIN, null, CODEVIEWER); + } + + @Test + public void fail_if_user_does_not_exist() { + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("User with login 'unknown-login' is not found"); + + newRequest("unknown-login", permissionTemplate.getKee(), CODEVIEWER); + } + + @Test + public void fail_if_template_key_does_not_exist() { + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("Permission template with key 'unknown-key' is not found"); + + newRequest(USER_LOGIN, "unknown-key", CODEVIEWER); + } + + private void newRequest(@Nullable String userLogin, @Nullable String templateKey, @Nullable String permission) { + TestRequest request = ws.newRequest(); + if (userLogin != null) { + request.setParam(Parameters.PARAM_USER_LOGIN, userLogin); + } + if (templateKey != null) { + request.setParam(Parameters.PARAM_TEMPLATE_KEY, templateKey); + } + if (permission != null) { + request.setParam(Parameters.PARAM_PERMISSION, permission); + } + + request.execute(); + } + + private void commit() { + dbSession.commit(); + } + + private UserDto insertUser(UserDto userDto) { + return dbClient.userDao().insert(dbSession, userDto.setActive(true)); + } + + private PermissionTemplateDto insertPermissionTemplate(PermissionTemplateDto permissionTemplate) { + return dbClient.permissionTemplateDao().insertPermissionTemplate(permissionTemplate.getName(), permissionTemplate.getDescription(), permissionTemplate.getKeyPattern()); + } + + private List getLoginsInTemplateAndPermission(long templateId, String permission) { + PermissionQuery permissionQuery = PermissionQuery.builder().permission(permission).membership(IN).build(); + return from(dbClient.permissionTemplateDao() + .selectUsers(dbSession, permissionQuery, templateId, 0, Integer.MAX_VALUE)) + .transform(UserWithPermissionToUserLogin.INSTANCE) + .toList(); + } + + private enum UserWithPermissionToUserLogin implements Function { + INSTANCE; + + @Override + public String apply(@Nonnull UserWithPermissionDto userWithPermission) { + return userWithPermission.getLogin(); + } + + } +} 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 29496c25b3f..7952db5d1d8 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(14); + assertThat(container.size()).isEqualTo(15); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveGroupActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveGroupActionTest.java index 4e85fdb1efe..016cf0aab0d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveGroupActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveGroupActionTest.java @@ -35,7 +35,6 @@ import org.sonar.db.component.ComponentDto; import org.sonar.db.user.GroupDto; import org.sonar.server.component.ComponentFinder; import org.sonar.server.exceptions.BadRequestException; -import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.ServerException; import org.sonar.server.permission.PermissionChange; import org.sonar.server.permission.PermissionUpdater; @@ -145,7 +144,7 @@ public class RemoveGroupActionTest { @Test public void fail_when_project_does_not_exist() throws Exception { - expectedException.expect(NotFoundException.class); + expectedException.expect(BadRequestException.class); newRequest() .setParam(PARAM_GROUP_NAME, "sonar-administrators") @@ -208,7 +207,7 @@ public class RemoveGroupActionTest { @Test public void fail_when_group_id_does_not_exist() throws Exception { - expectedException.expect(NotFoundException.class); + expectedException.expect(BadRequestException.class); expectedException.expectMessage("Group with id '42' is not found"); newRequest() 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 8a23ed1562f..eb084a0de38 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 @@ -218,20 +218,29 @@ public class PermissionTemplateDao implements Dao { } } + /** + * @deprecated since 5.2 use the method with dbSession instead + */ + @Deprecated public void insertUserPermission(Long templateId, Long userId, String permission) { + DbSession session = myBatis.openSession(false); + try { + insertUserPermission(session, templateId, userId, permission); + } finally { + MyBatis.closeQuietly(session); + } + } + + public void insertUserPermission(DbSession session, Long templateId, Long userId, String permission) { PermissionTemplateUserDto permissionTemplateUser = new PermissionTemplateUserDto() .setTemplateId(templateId) .setUserId(userId) .setPermission(permission) .setCreatedAt(now()) .setUpdatedAt(now()); - SqlSession session = myBatis.openSession(false); - try { - mapper(session).insertUserPermission(permissionTemplateUser); - session.commit(); - } finally { - MyBatis.closeQuietly(session); - } + + mapper(session).insertUserPermission(permissionTemplateUser); + session.commit(); } public void deleteUserPermission(Long templateId, Long userId, String permission) { diff --git a/sonar-db/src/test/java/org/sonar/db/permission/PermissionTemplateTesting.java b/sonar-db/src/test/java/org/sonar/db/permission/PermissionTemplateTesting.java new file mode 100644 index 00000000000..412537ded13 --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/permission/PermissionTemplateTesting.java @@ -0,0 +1,37 @@ +/* + * 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.db.permission; + +import java.util.Date; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.apache.commons.lang.RandomStringUtils.randomAscii; +import static org.apache.commons.lang.math.RandomUtils.nextLong; + +public class PermissionTemplateTesting { + public static PermissionTemplateDto newPermissionTemplateDto() { + return new PermissionTemplateDto() + .setName(randomAlphanumeric(60)) + .setDescription(randomAscii(500)) + .setCreatedAt(new Date(nextLong())) + .setUpdatedAt(new Date(nextLong())); + } +} -- 2.39.5