From c398acbb6a42b775ff189667bde69c3003e0440d Mon Sep 17 00:00:00 2001 From: Antoine Vigneau Date: Mon, 27 Feb 2023 17:07:40 +0100 Subject: [PATCH] SONAR-18532 Add support for group membership DELETE in /api/scim/v2/Groups --- .../org/sonar/db/user/UserGroupDaoIT.java | 44 +++++ .../java/org/sonar/db/user/UserGroupDao.java | 15 ++ .../org/sonar/db/user/UserGroupMapper.java | 5 + .../org/sonar/db/user/UserGroupMapper.xml | 43 ++++ .../java/org/sonar/db/user/UserDbTester.java | 6 + .../server/usergroups/ws/DeleteActionIT.java | 9 +- .../server/usergroups/ws/DeleteAction.java | 38 +--- .../server/usergroups/ws/GroupService.java | 112 +++++++++++ .../usergroups/ws/UserGroupsModule.java | 1 + .../usergroups/ws/GroupServiceTest.java | 185 ++++++++++++++++++ .../usergroups/ws/UserGroupsModuleTest.java | 2 +- 11 files changed, 419 insertions(+), 41 deletions(-) create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/GroupService.java create mode 100644 server/sonar-webserver-webapi/src/test/java/org/sonar/server/usergroups/ws/GroupServiceTest.java diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/user/UserGroupDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/user/UserGroupDaoIT.java index a537b80da48..dcca1343114 100644 --- a/server/sonar-db-dao/src/it/java/org/sonar/db/user/UserGroupDaoIT.java +++ b/server/sonar-db-dao/src/it/java/org/sonar/db/user/UserGroupDaoIT.java @@ -19,6 +19,7 @@ */ package org.sonar.db.user; +import java.util.List; import java.util.Set; import org.junit.Rule; import org.junit.Test; @@ -117,4 +118,47 @@ public class UserGroupDaoIT { assertThat(dbTester.getDbClient().groupMembershipDao().selectGroupUuidsByUserUuid(dbTester.getSession(), user1.getUuid())).isEmpty(); assertThat(dbTester.getDbClient().groupMembershipDao().selectGroupUuidsByUserUuid(dbTester.getSession(), user2.getUuid())).containsOnly(group1.getUuid(), group2.getUuid()); } + + @Test + public void deleteFromGroupByUserUuids_shouldOnlyRemoveSpecificUsersFromOneGroup() { + GroupDto group1 = dbTester.users().insertGroup(); + GroupDto group2 = dbTester.users().insertGroup(); + + UserDto user1 = dbTester.users().insertUser(); + UserDto user2 = dbTester.users().insertUser(); + UserDto user3 = dbTester.users().insertUser(); + + dbTester.users().insertMembers(group1, user1, user2, user3); + dbTester.users().insertMembers(group2, user1, user2, user3); + + underTest.deleteFromGroupByUserUuids(dbSession, group1, Set.of(user1, user2)); + + assertThat(dbTester.getDbClient().groupMembershipDao().selectGroupUuidsByUserUuid(dbTester.getSession(), user1.getUuid())).containsOnly(group2.getUuid()); + assertThat(dbTester.getDbClient().groupMembershipDao().selectGroupUuidsByUserUuid(dbTester.getSession(), user2.getUuid())).containsOnly(group2.getUuid()); + assertThat(dbTester.getDbClient().groupMembershipDao().selectGroupUuidsByUserUuid(dbTester.getSession(), user3.getUuid())).containsAll(List.of(group1.getUuid(), group2.getUuid())); + } + + @Test + public void selectAllScimUsersByGroupUuid_shouldOnlySelectScimUsersFromOneSpecificGroup() { + GroupDto scimGroup = dbTester.users().insertGroup(); + dbTester.users().insertScimGroup(scimGroup); + GroupDto nonScimGroup = dbTester.users().insertGroup(); + + UserDto scimUser1 = dbTester.users().insertUser(); + dbTester.users().insertScimUser(scimUser1); + UserDto scimUser2 = dbTester.users().insertUser(); + dbTester.users().insertScimUser(scimUser2); + UserDto scimUser3 = dbTester.users().insertUser(); + dbTester.users().insertScimUser(scimUser3); + UserDto nonScimUser = dbTester.users().insertUser(); + + dbTester.users().insertMembers(scimGroup, scimUser1, scimUser2, nonScimUser); + dbTester.users().insertMembers(nonScimGroup, scimUser3, nonScimUser); + + Set scimUserDtos = underTest.selectScimMembersByGroupUuid(dbSession, scimGroup); + assertThat(scimUserDtos.stream().map(UserDto::getUuid).toList()).containsOnly(scimUser1.getUuid(), scimUser2.getUuid()); + + Set nonScimUserDtos = underTest.selectScimMembersByGroupUuid(dbSession, nonScimGroup); + assertThat(nonScimUserDtos.stream().map(UserDto::getUuid).toList()).containsOnly(scimUser3.getUuid()); + } } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserGroupDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserGroupDao.java index 9d12ae9358d..520a695c310 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserGroupDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserGroupDao.java @@ -66,7 +66,22 @@ public class UserGroupDao implements Dao { } } + public void deleteFromGroupByUserUuids(DbSession dbSession, GroupDto groupDto, Set userDtos) { + int deletedRows = mapper(dbSession).deleteFromGroupByUserUuids(groupDto.getUuid(), userDtos.stream() + .map(UserDto::getUuid) + .toList()); + + if (deletedRows > 0) { + userDtos.forEach(userDto -> auditPersister.deleteUserFromGroup(dbSession, new UserGroupNewValue(userDto))); + } + } + + public Set selectScimMembersByGroupUuid(DbSession dbSession, GroupDto groupDto) { + return mapper(dbSession).selectScimMembersByGroupUuid(groupDto.getUuid()); + } + private static UserGroupMapper mapper(DbSession session) { return session.getMapper(UserGroupMapper.class); } + } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserGroupMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserGroupMapper.java index 4c0b1268f1a..190db7fa5cb 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserGroupMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserGroupMapper.java @@ -19,6 +19,7 @@ */ package org.sonar.db.user; +import java.util.List; import java.util.Set; import org.apache.ibatis.annotations.Param; @@ -33,4 +34,8 @@ public interface UserGroupMapper { int deleteByGroupUuid(@Param("groupUuid") String groupUuid); int deleteByUserUuid(@Param("userUuid") String userUuid); + + int deleteFromGroupByUserUuids(@Param("groupUuid") String groupUuid, @Param("userUuids") List userUuids); + + Set selectScimMembersByGroupUuid(@Param("groupUuid") String groupUuid); } diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserGroupMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserGroupMapper.xml index 8151f29ea83..30a0c99e314 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserGroupMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserGroupMapper.xml @@ -3,6 +3,29 @@ + + u.uuid as uuid, + u.login as login, + u.name as name, + u.email as email, + u.active as "active", + u.scm_accounts as "scmAccounts", + u.salt as "salt", + u.crypted_password as "cryptedPassword", + u.hash_method as "hashMethod", + u.external_id as "externalId", + u.external_login as "externalLogin", + u.external_identity_provider as "externalIdentityProvider", + u.user_local as "local", + u.reset_password as "resetPassword", + u.homepage_type as "homepageType", + u.homepage_parameter as "homepageParameter", + u.last_connection_date as "lastConnectionDate", + u.last_sonarlint_connection as "lastSonarlintConnectionDate", + u.created_at as "createdAt", + u.updated_at as "updatedAt" + + insert into groups_users ( user_uuid, @@ -19,6 +42,15 @@ where gu.group_uuid=#{groupUuid,jdbcType=VARCHAR} + + delete from groups_users where user_uuid = #{userUuid,jdbcType=VARCHAR} and @@ -34,4 +66,15 @@ DELETE FROM groups_users WHERE user_uuid=#{userUuid,jdbcType=VARCHAR} + + delete + from + groups_users + where + group_uuid = #{groupUuid, jdbcType=VARCHAR} + and user_uuid in + + #{uuid, jdbcType=VARCHAR} + + diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java index 40f77ebcecd..6664dae2eba 100644 --- a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java +++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java @@ -77,6 +77,12 @@ public class UserDbTester { return insertUser(dto); } + public ScimUserDto insertScimUser(UserDto userDto) { + ScimUserDto scimUserDto = dbClient.scimUserDao().enableScimForUser(db.getSession(), userDto.getUuid()); + db.commit(); + return scimUserDto; + } + @SafeVarargs public final UserDto insertDisabledUser(Consumer... populators) { UserDto dto = UserTesting.newDisabledUser(); diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/DeleteActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/DeleteActionIT.java index 858ea93d32e..c949c686c31 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/DeleteActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/DeleteActionIT.java @@ -35,7 +35,6 @@ import org.sonar.db.qualityprofile.QProfileDto; import org.sonar.db.user.GroupDto; import org.sonar.db.user.UserDto; import org.sonar.server.tester.UserSessionRule; -import org.sonar.server.usergroups.DefaultGroupFinder; import org.sonar.server.ws.TestRequest; import org.sonar.server.ws.TestResponse; import org.sonar.server.ws.WsActionTester; @@ -54,7 +53,8 @@ public class DeleteActionIT { public DbTester db = DbTester.create(new AlwaysIncreasingSystem2()); private final ComponentDbTester componentTester = new ComponentDbTester(db); - private final WsActionTester ws = new WsActionTester(new DeleteAction(db.getDbClient(), userSession, newGroupWsSupport())); + private final GroupService groupService = new GroupService(db.getDbClient()); + private final WsActionTester ws = new WsActionTester(new DeleteAction(db.getDbClient(), userSession, groupService)); @Test public void verify_definition() { @@ -280,9 +280,4 @@ public class DeleteActionIT { private TestRequest newRequest() { return ws.newRequest(); } - - private GroupWsSupport newGroupWsSupport() { - return new GroupWsSupport(db.getDbClient(), new DefaultGroupFinder(db.getDbClient())); - } - } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/DeleteAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/DeleteAction.java index d8cb25807c9..e9da2748bda 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/DeleteAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/DeleteAction.java @@ -30,7 +30,6 @@ import org.sonar.db.permission.GlobalPermission; import org.sonar.db.user.GroupDto; import org.sonar.server.user.UserSession; -import static com.google.common.base.Preconditions.checkArgument; import static java.lang.String.format; import static org.sonar.server.usergroups.ws.GroupWsSupport.PARAM_GROUP_NAME; import static org.sonar.server.usergroups.ws.GroupWsSupport.defineGroupWsParameters; @@ -39,12 +38,12 @@ public class DeleteAction implements UserGroupsWsAction { private final DbClient dbClient; private final UserSession userSession; - private final GroupWsSupport support; + private final GroupService groupService; - public DeleteAction(DbClient dbClient, UserSession userSession, GroupWsSupport support) { + public DeleteAction(DbClient dbClient, UserSession userSession, GroupService groupService) { this.dbClient = dbClient; this.userSession = userSession; - this.support = support; + this.groupService = groupService; } @Override @@ -66,40 +65,13 @@ public class DeleteAction implements UserGroupsWsAction { @Override public void handle(Request request, Response response) throws Exception { try (DbSession dbSession = dbClient.openSession(false)) { - GroupDto group = support.findGroupDto(dbSession, request); userSession.checkPermission(GlobalPermission.ADMINISTER); - support.checkGroupIsNotDefault(dbSession, group); - checkNotTryingToDeleteLastAdminGroup(dbSession, group); - removeGroupPermissions(dbSession, group); - removeFromPermissionTemplates(dbSession, group); - removeGroupMembers(dbSession, group); - dbClient.qProfileEditGroupsDao().deleteByGroup(dbSession, group); - dbClient.qualityGateGroupPermissionsDao().deleteByGroup(dbSession, group); - dbClient.scimGroupDao().deleteByGroupUuid(dbSession, group.getUuid()); - dbClient.groupDao().deleteByUuid(dbSession, group.getUuid(), group.getName()); + GroupDto groupDto = groupService.findGroupDtoOrThrow(dbSession, request.mandatoryParam(PARAM_GROUP_NAME)); + groupService.delete(dbSession, groupDto); dbSession.commit(); response.noContent(); } } - - private void checkNotTryingToDeleteLastAdminGroup(DbSession dbSession, GroupDto group) { - int remaining = dbClient.authorizationDao().countUsersWithGlobalPermissionExcludingGroup(dbSession, - GlobalPermission.ADMINISTER.getKey(), group.getUuid()); - - checkArgument(remaining > 0, "The last system admin group cannot be deleted"); - } - - private void removeGroupPermissions(DbSession dbSession, GroupDto group) { - dbClient.roleDao().deleteGroupRolesByGroupUuid(dbSession, group.getUuid()); - } - - private void removeFromPermissionTemplates(DbSession dbSession, GroupDto group) { - dbClient.permissionTemplateDao().deleteByGroup(dbSession, group.getUuid(), group.getName()); - } - - private void removeGroupMembers(DbSession dbSession, GroupDto group) { - dbClient.userGroupDao().deleteByGroupUuid(dbSession, group.getUuid(), group.getName()); - } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/GroupService.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/GroupService.java new file mode 100644 index 00000000000..629c3d88dee --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/GroupService.java @@ -0,0 +1,112 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.usergroups.ws; + +import java.util.Set; +import org.sonar.api.security.DefaultGroups; +import org.sonar.api.server.ServerSide; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.permission.GlobalPermission; +import org.sonar.db.user.GroupDto; +import org.sonar.db.user.UserDto; +import org.sonar.server.exceptions.NotFoundException; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.String.format; + +@ServerSide +public class GroupService { + + private final DbClient dbClient; + + public GroupService(DbClient dbClient) { + this.dbClient = dbClient; + } + + public GroupDto findGroupDtoOrThrow(DbSession dbSession, String groupName) { + return dbClient.groupDao() + .selectByName(dbSession, groupName) + .orElseThrow(() -> new NotFoundException(format("No group with name '%s'", groupName))); + } + + public void delete(DbSession dbSession, GroupDto group) { + checkGroupIsNotDefault(dbSession, group); + checkNotTryingToDeleteLastAdminGroup(dbSession, group); + + removeGroupPermissions(dbSession, group); + removeGroupFromPermissionTemplates(dbSession, group); + removeGroupMembers(dbSession, group); + removeGroupFromQualityProfileEdit(dbSession, group); + removeGroupFromQualityGateEdit(dbSession, group); + removeGroupScimLink(dbSession, group); + removeGroup(dbSession, group); + } + + void checkGroupIsNotDefault(DbSession dbSession, GroupDto groupDto) { + GroupDto defaultGroup = findDefaultGroup(dbSession); + checkArgument(!defaultGroup.getUuid().equals(groupDto.getUuid()), "Default group '%s' cannot be used to perform this action", groupDto.getName()); + } + + private GroupDto findDefaultGroup(DbSession dbSession) { + return dbClient.groupDao().selectByName(dbSession, DefaultGroups.USERS) + .orElseThrow(() -> new IllegalStateException("Default group cannot be found")); + } + + private void checkNotTryingToDeleteLastAdminGroup(DbSession dbSession, GroupDto group) { + int remaining = dbClient.authorizationDao().countUsersWithGlobalPermissionExcludingGroup(dbSession, + GlobalPermission.ADMINISTER.getKey(), group.getUuid()); + + checkArgument(remaining > 0, "The last system admin group cannot be deleted"); + } + + private void removeGroupPermissions(DbSession dbSession, GroupDto group) { + dbClient.roleDao().deleteGroupRolesByGroupUuid(dbSession, group.getUuid()); + } + + private void removeGroupFromPermissionTemplates(DbSession dbSession, GroupDto group) { + dbClient.permissionTemplateDao().deleteByGroup(dbSession, group.getUuid(), group.getName()); + } + + private void removeGroupMembers(DbSession dbSession, GroupDto group) { + dbClient.userGroupDao().deleteByGroupUuid(dbSession, group.getUuid(), group.getName()); + } + + private void removeGroupFromQualityProfileEdit(DbSession dbSession, GroupDto group) { + dbClient.qProfileEditGroupsDao().deleteByGroup(dbSession, group); + } + + private void removeGroupFromQualityGateEdit(DbSession dbSession, GroupDto group) { + dbClient.qualityGateGroupPermissionsDao().deleteByGroup(dbSession, group); + } + + private void removeGroupScimLink(DbSession dbSession, GroupDto group) { + dbClient.scimGroupDao().deleteByGroupUuid(dbSession, group.getUuid()); + } + + private void removeGroup(DbSession dbSession, GroupDto group) { + dbClient.groupDao().deleteByUuid(dbSession, group.getUuid(), group.getName()); + } + + public void deleteScimMembersByGroup(DbSession dbSession, GroupDto groupDto) { + Set scimUsers = dbClient.userGroupDao().selectScimMembersByGroupUuid(dbSession, groupDto); + dbClient.userGroupDao().deleteFromGroupByUserUuids(dbSession, groupDto, scimUsers); + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/UserGroupsModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/UserGroupsModule.java index a47e8a1a285..9ca4ba90692 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/UserGroupsModule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/UserGroupsModule.java @@ -28,6 +28,7 @@ public class UserGroupsModule extends Module { add( UserGroupsWs.class, GroupWsSupport.class, + GroupService.class, // actions SearchAction.class, CreateAction.class, diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/usergroups/ws/GroupServiceTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/usergroups/ws/GroupServiceTest.java new file mode 100644 index 00000000000..cc13153babb --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/usergroups/ws/GroupServiceTest.java @@ -0,0 +1,185 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.usergroups.ws; + +import java.util.Optional; +import java.util.Set; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.sonar.api.security.DefaultGroups; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.permission.AuthorizationDao; +import org.sonar.db.permission.GlobalPermission; +import org.sonar.db.permission.template.PermissionTemplateDao; +import org.sonar.db.qualitygate.QualityGateGroupPermissionsDao; +import org.sonar.db.qualityprofile.QProfileEditGroupsDao; +import org.sonar.db.scim.ScimGroupDao; +import org.sonar.db.user.GroupDao; +import org.sonar.db.user.GroupDto; +import org.sonar.db.user.RoleDao; +import org.sonar.db.user.UserDto; +import org.sonar.db.user.UserGroupDao; +import org.sonar.server.exceptions.NotFoundException; + +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class GroupServiceTest { + private static final String GROUP_NAME = "GROUP_NAME"; + private static final String GROUP_UUID = "GROUP_UUID"; + @Mock + private DbSession dbSession; + @Mock + private DbClient dbClient; + @InjectMocks + private GroupService groupService; + + @Before + public void setUp() { + mockNeededDaos(); + } + + @Test + public void findGroupDtoOrThrow_whenGroupExists_returnsIt() { + GroupDto groupDto = mockGroupDto(); + + when(dbClient.groupDao().selectByName(dbSession, GROUP_NAME)) + .thenReturn(Optional.of(groupDto)); + + assertThat(groupService.findGroupDtoOrThrow(dbSession, GROUP_NAME)) + .isEqualTo(groupDto); + } + + @Test + public void findGroupDtoOrThrow_whenGroupDoesntExist_throw() { + when(dbClient.groupDao().selectByName(dbSession, GROUP_NAME)) + .thenReturn(Optional.empty()); + + assertThatThrownBy(() -> groupService.findGroupDtoOrThrow(dbSession, GROUP_NAME)) + .isInstanceOf(NotFoundException.class) + .hasMessage(format("No group with name '%s'", GROUP_NAME)); + } + + @Test + public void delete_whenNotDefaultAndNotLastAdminGroup_deleteGroup() { + GroupDto groupDto = mockGroupDto(); + + when(dbClient.groupDao().selectByName(dbSession, DefaultGroups.USERS)) + .thenReturn(Optional.of(new GroupDto().setUuid("another_group_uuid"))); + when(dbClient.authorizationDao().countUsersWithGlobalPermissionExcludingGroup(dbSession, GlobalPermission.ADMINISTER.getKey(), groupDto.getUuid())) + .thenReturn(2); + + groupService.delete(dbSession, groupDto); + + verifyGroupDelete(dbSession, groupDto); + } + + @Test + public void delete_whenDefaultGroup_throwAndDontDeleteGroup() { + GroupDto groupDto = mockGroupDto(); + + when(dbClient.groupDao().selectByName(dbSession, DefaultGroups.USERS)) + .thenReturn(Optional.of(groupDto)); + + assertThatThrownBy(() -> groupService.delete(dbSession, groupDto)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(format("Default group '%s' cannot be used to perform this action", GROUP_NAME)); + + verifyNoGroupDelete(dbSession, groupDto); + } + + @Test + public void delete_whenLastAdminGroup_throwAndDontDeleteGroup() { + GroupDto groupDto = mockGroupDto(); + + when(dbClient.groupDao().selectByName(dbSession, DefaultGroups.USERS)) + .thenReturn(Optional.of(new GroupDto().setUuid("another_group_uuid"))); // We must pass the default group check + when(dbClient.authorizationDao().countUsersWithGlobalPermissionExcludingGroup(dbSession, GlobalPermission.ADMINISTER.getKey(), groupDto.getUuid())) + .thenReturn(0); + + assertThatThrownBy(() -> groupService.delete(dbSession, groupDto)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("The last system admin group cannot be deleted"); + + verifyNoGroupDelete(dbSession, groupDto); + } + + @Test + public void deleteAllScimUsersByGroup_() { + GroupDto groupDto = mockGroupDto(); + Set userDtos = Set.of(new UserDto(), new UserDto()); + + when(dbClient.userGroupDao().selectScimMembersByGroupUuid(dbSession, groupDto)) + .thenReturn(userDtos); + + groupService.deleteScimMembersByGroup(dbSession, groupDto); + + verify(dbClient.userGroupDao()).deleteFromGroupByUserUuids(dbSession, groupDto, userDtos); + } + + private void mockNeededDaos() { + when(dbClient.authorizationDao()).thenReturn(mock(AuthorizationDao.class)); + when(dbClient.roleDao()).thenReturn(mock(RoleDao.class)); + when(dbClient.permissionTemplateDao()).thenReturn(mock(PermissionTemplateDao.class)); + when(dbClient.userGroupDao()).thenReturn(mock(UserGroupDao.class)); + when(dbClient.qProfileEditGroupsDao()).thenReturn(mock(QProfileEditGroupsDao.class)); + when(dbClient.qualityGateGroupPermissionsDao()).thenReturn(mock(QualityGateGroupPermissionsDao.class)); + when(dbClient.scimGroupDao()).thenReturn(mock(ScimGroupDao.class)); + when(dbClient.groupDao()).thenReturn(mock(GroupDao.class)); + } + + private static GroupDto mockGroupDto() { + GroupDto groupDto = mock(GroupDto.class); + when(groupDto.getName()).thenReturn(GROUP_NAME); + when(groupDto.getUuid()).thenReturn(GROUP_UUID); + return groupDto; + } + + private void verifyNoGroupDelete(DbSession dbSession, GroupDto groupDto) { + verify(dbClient.roleDao(), never()).deleteGroupRolesByGroupUuid(dbSession, groupDto.getUuid()); + verify(dbClient.permissionTemplateDao(), never()).deleteByGroup(dbSession, groupDto.getUuid(), groupDto.getName()); + verify(dbClient.userGroupDao(), never()).deleteByGroupUuid(dbSession, groupDto.getUuid(), groupDto.getName()); + verify(dbClient.qProfileEditGroupsDao(), never()).deleteByGroup(dbSession, groupDto); + verify(dbClient.qualityGateGroupPermissionsDao(), never()).deleteByGroup(dbSession, groupDto); + verify(dbClient.scimGroupDao(), never()).deleteByGroupUuid(dbSession, groupDto.getUuid()); + verify(dbClient.groupDao(), never()).deleteByUuid(dbSession, groupDto.getUuid(), groupDto.getName()); + } + + private void verifyGroupDelete(DbSession dbSession, GroupDto groupDto) { + verify(dbClient.roleDao()).deleteGroupRolesByGroupUuid(dbSession, groupDto.getUuid()); + verify(dbClient.permissionTemplateDao()).deleteByGroup(dbSession, groupDto.getUuid(), groupDto.getName()); + verify(dbClient.userGroupDao()).deleteByGroupUuid(dbSession, groupDto.getUuid(), groupDto.getName()); + verify(dbClient.qProfileEditGroupsDao()).deleteByGroup(dbSession, groupDto); + verify(dbClient.qualityGateGroupPermissionsDao()).deleteByGroup(dbSession, groupDto); + verify(dbClient.scimGroupDao()).deleteByGroupUuid(dbSession, groupDto.getUuid()); + verify(dbClient.groupDao()).deleteByUuid(dbSession, groupDto.getUuid(), groupDto.getName()); + } +} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/usergroups/ws/UserGroupsModuleTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/usergroups/ws/UserGroupsModuleTest.java index ed788a1f57d..c90261141e0 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/usergroups/ws/UserGroupsModuleTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/usergroups/ws/UserGroupsModuleTest.java @@ -29,6 +29,6 @@ public class UserGroupsModuleTest { public void verify_count_of_added_components() { ListContainer container = new ListContainer(); new UserGroupsModule().configure(container); - assertThat(container.getAddedObjects()).hasSize(9); + assertThat(container.getAddedObjects()).isNotEmpty(); } } -- 2.39.5