From: Simon Brandhof Date: Thu, 3 Nov 2016 14:40:04 +0000 (+0100) Subject: SONAR-6912 do not remove a group member if no more admins X-Git-Tag: 6.2-RC1~216 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=806a8066d12e5589b1833d368f989bf64456b21c;p=sonarqube.git SONAR-6912 do not remove a group member if no more admins --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/RemoveUserAction.java b/server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/RemoveUserAction.java index 5726167f209..d1dfc46ca6b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/RemoveUserAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/RemoveUserAction.java @@ -26,6 +26,7 @@ import org.sonar.api.server.ws.WebService.NewController; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.user.UserDto; +import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.organization.DefaultOrganizationProvider; import org.sonar.server.user.UserSession; @@ -76,6 +77,8 @@ public class RemoveUserAction implements UserGroupsWsAction { String login = request.mandatoryParam(PARAM_LOGIN); UserDto user = getUser(dbSession, login); + ensureLastAdminIsNotRemoved(dbSession, group, user); + dbClient.userGroupDao().delete(dbSession, group.getId(), user.getId()); dbClient.userDao().updateRootFlagFromPermissions(dbSession, user.getId(), defaultOrganizationProvider.get().getUuid()); dbSession.commit(); @@ -84,6 +87,17 @@ public class RemoveUserAction implements UserGroupsWsAction { } } + /** + * Ensure that there are still users with admin global permission if user is removed from the group. + */ + private void ensureLastAdminIsNotRemoved(DbSession dbSession, GroupId group, UserDto user) { + int remainingAdmins = dbClient.authorizationDao().countUsersWithGlobalPermissionExcludingGroupMember(dbSession, + group.getOrganizationUuid(), SYSTEM_ADMIN, group.getId(), user.getId()); + if (remainingAdmins == 0) { + throw new BadRequestException("The last administrator user cannot be removed"); + } + } + private UserDto getUser(DbSession dbSession, String userLogin) { return checkFound(dbClient.userDao().selectActiveUserByLogin(dbSession, userLogin), "User with login '%s' is not found'", userLogin); diff --git a/server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/RemoveUserActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/RemoveUserActionTest.java index 4ff9e6cbbe6..423d1fd91d3 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/RemoveUserActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/RemoveUserActionTest.java @@ -33,6 +33,7 @@ import org.sonar.db.user.UserDto; import org.sonar.db.user.UserMembershipDto; import org.sonar.db.user.UserMembershipQuery; import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.organization.TestDefaultOrganizationProvider; import org.sonar.server.tester.UserSessionRule; @@ -63,6 +64,9 @@ public class RemoveUserActionTest { @Test public void does_nothing_if_user_is_not_in_group() throws Exception { + // keep an administrator + insertAnAdministratorInDefaultOrganization(); + GroupDto group = db.users().insertGroup(db.getDefaultOrganization(), "admins"); UserDto user = db.users().insertUser("my-admin"); loginAsAdminOnDefaultOrganization(); @@ -78,6 +82,8 @@ public class RemoveUserActionTest { @Test public void remove_user_by_group_id() throws Exception { + // keep an administrator + insertAnAdministratorInDefaultOrganization(); GroupDto users = db.users().insertGroup(db.getDefaultOrganization(), "users"); UserDto user = db.users().insertUser("my-admin"); db.users().insertMember(users, user); @@ -94,8 +100,9 @@ public class RemoveUserActionTest { @Test public void remove_user_by_group_name_in_default_organization() throws Exception { - GroupDto group = db.users().insertGroup(db.getDefaultOrganization(), "group_name"); - UserDto user = db.users().insertUser("user_login"); + insertAnAdministratorInDefaultOrganization(); + GroupDto group = db.users().insertGroup(db.getDefaultOrganization(), "a_group"); + UserDto user = db.users().insertUser("a_user"); db.users().insertMember(group, user); loginAsAdminOnDefaultOrganization(); @@ -112,8 +119,10 @@ public class RemoveUserActionTest { public void remove_user_by_group_name_in_specific_organization() throws Exception { OrganizationDto org = db.organizations().insert(); GroupDto group = db.users().insertGroup(org, "a_group"); - UserDto user = db.users().insertUser("user_login"); + UserDto user = db.users().insertUser("a_user"); db.users().insertMember(group, user); + // keep an administrator + db.users().insertAdminByUserPermission(org); loginAsAdmin(org); @@ -129,6 +138,9 @@ public class RemoveUserActionTest { @Test public void remove_user_only_from_one_group() throws Exception { + // keep an administrator + insertAnAdministratorInDefaultOrganization(); + OrganizationDto defaultOrg = db.getDefaultOrganization(); GroupDto users = db.users().insertGroup(defaultOrg, "user"); GroupDto admins = db.users().insertGroup(defaultOrg, "admins"); @@ -174,6 +186,9 @@ public class RemoveUserActionTest { @Test public void sets_root_flag_to_false_when_removing_user_from_group_of_default_organization_with_admin_permission() throws Exception { + // keep an administrator + insertAnAdministratorInDefaultOrganization(); + GroupDto adminGroup = db.users().insertAdminGroup(); UserDto user1 = db.users().insertRootByGroupPermission("user1", adminGroup); UserDto user2 = db.users().insertRootByGroupPermission("user2", adminGroup); @@ -244,7 +259,24 @@ public class RemoveUserActionTest { newRequest() .setParam("id", group.getId().toString()) - .setParam("login", user.getLogin()) + .setParam("login", user.getLogin()); + } + + @Test + public void fail_to_remove_the_last_administrator() throws Exception { + OrganizationDto org = db.organizations().insert(); + GroupDto adminGroup = db.users().insertGroup(org, "sonar-admins"); + db.users().insertPermissionOnGroup(adminGroup, GlobalPermissions.SYSTEM_ADMIN); + UserDto adminUser = db.users().insertUser("the-single-admin"); + db.users().insertMember(adminGroup, adminUser); + loginAsAdmin(org); + + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("The last administrator user cannot be removed"); + + newRequest() + .setParam("id", adminGroup.getId().toString()) + .setParam("login", adminUser.getLogin()) .execute(); } @@ -299,4 +331,7 @@ public class RemoveUserActionTest { .anyMatch(dto -> dto.getLogin().equals(userDto.getLogin())); } + private UserDto insertAnAdministratorInDefaultOrganization() { + return db.users().insertAdminByUserPermission(db.getDefaultOrganization()); + } } diff --git a/sonar-db/src/main/java/org/sonar/db/permission/AuthorizationDao.java b/sonar-db/src/main/java/org/sonar/db/permission/AuthorizationDao.java index 9b60f610117..50845b65cea 100644 --- a/sonar-db/src/main/java/org/sonar/db/permission/AuthorizationDao.java +++ b/sonar-db/src/main/java/org/sonar/db/permission/AuthorizationDao.java @@ -79,7 +79,7 @@ public class AuthorizationDao implements Dao { } /** - * The number of users who will still have the permission when the group {@code excludedGroupId} + * The number of users who will still have the permission if the group {@code excludedGroupId} * is deleted. The anyone virtual group is not taken into account. */ public int countUsersWithGlobalPermissionExcludingGroup(DbSession dbSession, String organizationUuid, @@ -88,7 +88,7 @@ public class AuthorizationDao implements Dao { } /** - * The number of users who will still have the permission when the user {@code excludedUserId} + * The number of users who will still have the permission if the user {@code excludedUserId} * is deleted. The anyone virtual group is not taken into account. */ public int countUsersWithGlobalPermissionExcludingUser(DbSession dbSession, String organizationUuid, @@ -96,6 +96,17 @@ public class AuthorizationDao implements Dao { return mapper(dbSession).countUsersWithGlobalPermissionExcludingUser(organizationUuid, permission, excludedUSerId); } + /** + * The number of users who will still have the permission if the user {@code userId} + * is removed from group {@code groupId}. The anyone virtual group is not taken into account. + * Contrary to {@link #countUsersWithGlobalPermissionExcludingUser(DbSession, String, String, long)}, user + * still exists and may have the permission directly or through other groups. + */ + public int countUsersWithGlobalPermissionExcludingGroupMember(DbSession dbSession, String organizationUuid, + String permission, long groupId, long userId) { + return mapper(dbSession).countUsersWithGlobalPermissionExcludingGroupMember(organizationUuid, permission, groupId, userId); + } + public Set keepAuthorizedProjectIds(DbSession dbSession, Collection componentIds, @Nullable Integer userId, String role) { return executeLargeInputsIntoSet( componentIds, diff --git a/sonar-db/src/main/java/org/sonar/db/permission/AuthorizationMapper.java b/sonar-db/src/main/java/org/sonar/db/permission/AuthorizationMapper.java index df686f1cac6..5c7e2ae853e 100644 --- a/sonar-db/src/main/java/org/sonar/db/permission/AuthorizationMapper.java +++ b/sonar-db/src/main/java/org/sonar/db/permission/AuthorizationMapper.java @@ -43,6 +43,9 @@ public interface AuthorizationMapper { int countUsersWithGlobalPermissionExcludingUser(@Param("organizationUuid") String organizationUuid, @Param("permission") String permission, @Param("excludedUserId") long excludedUserId); + int countUsersWithGlobalPermissionExcludingGroupMember(@Param("organizationUuid") String organizationUuid, + @Param("permission") String permission, @Param("groupId") long groupId, @Param("userId") long userId); + Set keepAuthorizedProjectIdsForAnonymous(@Param("role") String role, @Param("componentIds") Collection componentIds); Set keepAuthorizedProjectIdsForUser(@Param("userId") long userId, @Param("role") String role, @Param("componentIds") Collection componentIds); diff --git a/sonar-db/src/main/resources/org/sonar/db/permission/AuthorizationMapper.xml b/sonar-db/src/main/resources/org/sonar/db/permission/AuthorizationMapper.xml index 37738662baf..2300f9d8c9e 100644 --- a/sonar-db/src/main/resources/org/sonar/db/permission/AuthorizationMapper.xml +++ b/sonar-db/src/main/resources/org/sonar/db/permission/AuthorizationMapper.xml @@ -120,6 +120,30 @@ ) remaining + +