]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21058 API v2 GET /authorizations/groups/{groupId}
authorWojtek Wajerowicz <115081248+wojciech-wajerowicz-sonarsource@users.noreply.github.com>
Tue, 21 Nov 2023 10:59:19 +0000 (11:59 +0100)
committersonartech <sonartech@sonarsource.com>
Thu, 23 Nov 2023 20:02:57 +0000 (20:02 +0000)
22 files changed:
server/sonar-webserver-common/src/main/java/org/sonar/server/common/group/service/GroupService.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/group/service/package-info.java [new file with mode: 0644]
server/sonar-webserver-common/src/test/java/org/sonar/server/common/group/service/GroupServiceTest.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/controller/DefaultGroupController.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/controller/GroupController.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/controller/package-info.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/response/RestGroupResponse.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/response/package-info.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java
server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/group/controller/DefaultGroupControllerTest.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/CreateActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/DeleteActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/ExternalGroupServiceIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/UpdateActionIT.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/CreateAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/DeleteAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/ExternalGroupService.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/GroupService.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/UpdateAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/UserGroupsModule.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/usergroups/ws/GroupServiceTest.java [deleted file]

diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/group/service/GroupService.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/group/service/GroupService.java
new file mode 100644 (file)
index 0000000..4e28d96
--- /dev/null
@@ -0,0 +1,179 @@
+/*
+ * 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.common.group.service;
+
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.sonar.api.security.DefaultGroups;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.user.UserGroupValidation;
+import org.sonar.core.util.UuidFactory;
+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.server.exceptions.BadRequestException;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.sonar.server.exceptions.BadRequestException.checkRequest;
+
+@ServerSide
+public class GroupService {
+
+  private final DbClient dbClient;
+  private final UuidFactory uuidFactory;
+
+  public GroupService(DbClient dbClient, UuidFactory uuidFactory) {
+    this.dbClient = dbClient;
+    this.uuidFactory = uuidFactory;
+  }
+
+  public Optional<GroupDto> findGroup(DbSession dbSession, String groupName) {
+    return dbClient.groupDao().selectByName(dbSession, groupName);
+  }
+
+  public Optional<GroupDto> findGroupByUuid(DbSession dbSession, String groupUuid) {
+    return Optional.ofNullable(dbClient.groupDao().selectByUuid(dbSession, groupUuid));
+  }
+
+  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);
+    removeExternalGroupMapping(dbSession, group);
+    removeGithubOrganizationGroup(dbSession, group);
+
+    removeGroup(dbSession, group);
+  }
+
+  public GroupDto updateGroup(DbSession dbSession, GroupDto group, @Nullable String newName) {
+    checkGroupIsNotDefault(dbSession, group);
+    return updateName(dbSession, group, newName);
+  }
+
+  public GroupDto updateGroup(DbSession dbSession, GroupDto group, @Nullable String newName, @Nullable String newDescription) {
+    checkGroupIsNotDefault(dbSession, group);
+    GroupDto withUpdatedName = updateName(dbSession, group, newName);
+    return updateDescription(dbSession, withUpdatedName, newDescription);
+  }
+
+  public GroupDto createGroup(DbSession dbSession, String name, @Nullable String description) {
+    validateGroupName(name);
+    checkNameDoesNotExist(dbSession, name);
+
+    GroupDto group = new GroupDto()
+      .setUuid(uuidFactory.create())
+      .setName(name)
+      .setDescription(description);
+    return dbClient.groupDao().insert(dbSession, group);
+  }
+
+  private GroupDto updateName(DbSession dbSession, GroupDto group, @Nullable String newName) {
+    if (newName != null && !newName.equals(group.getName())) {
+      validateGroupName(newName);
+      checkNameDoesNotExist(dbSession, newName);
+      group.setName(newName);
+      return dbClient.groupDao().update(dbSession, group);
+    }
+    return group;
+  }
+
+  private static void validateGroupName(String name) {
+    try {
+      UserGroupValidation.validateGroupName(name);
+    } catch (IllegalArgumentException e) {
+      BadRequestException.throwBadRequestException(e.getMessage());
+    }
+  }
+
+  private void checkNameDoesNotExist(DbSession dbSession, String name) {
+    // There is no database constraint on column groups.name
+    // because MySQL cannot create a unique index
+    // on a UTF-8 VARCHAR larger than 255 characters on InnoDB
+    checkRequest(!dbClient.groupDao().selectByName(dbSession, name).isPresent(), "Group '%s' already exists", name);
+  }
+
+  private GroupDto updateDescription(DbSession dbSession, GroupDto group, @Nullable String newDescription) {
+    if (newDescription != null) {
+      group.setDescription(newDescription);
+      return dbClient.groupDao().update(dbSession, group);
+    }
+    return group;
+  }
+
+  private 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 removeExternalGroupMapping(DbSession dbSession, GroupDto group) {
+    dbClient.externalGroupDao().deleteByGroupUuid(dbSession, group.getUuid());
+  }
+  private void removeGithubOrganizationGroup(DbSession dbSession, GroupDto group) {
+    dbClient.githubOrganizationGroupDao().deleteByGroupUuid(dbSession, group.getUuid());
+  }
+
+  private void removeGroup(DbSession dbSession, GroupDto group) {
+    dbClient.groupDao().deleteByUuid(dbSession, group.getUuid(), group.getName());
+  }
+
+}
diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/group/service/package-info.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/group/service/package-info.java
new file mode 100644 (file)
index 0000000..3e12624
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.common.group.service;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/group/service/GroupServiceTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/group/service/GroupServiceTest.java
new file mode 100644 (file)
index 0000000..f64f4e1
--- /dev/null
@@ -0,0 +1,357 @@
+/*
+ * 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.common.group.service;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Optional;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.sonar.api.security.DefaultGroups;
+import org.sonar.core.util.UuidFactory;
+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.provisioning.GithubOrganizationGroupDao;
+import org.sonar.db.qualitygate.QualityGateGroupPermissionsDao;
+import org.sonar.db.qualityprofile.QProfileEditGroupsDao;
+import org.sonar.db.scim.ScimGroupDao;
+import org.sonar.db.user.ExternalGroupDao;
+import org.sonar.db.user.GroupDao;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.RoleDao;
+import org.sonar.db.user.UserGroupDao;
+import org.sonar.server.exceptions.BadRequestException;
+
+import static java.lang.String.format;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
+import static org.mockito.ArgumentMatchers.eq;
+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(DataProviderRunner.class)
+public class GroupServiceTest {
+
+  private static final String GROUP_NAME = "GROUP_NAME";
+  private static final String GROUP_UUID = "GROUP_UUID";
+  private static final String DEFAULT_GROUP_NAME = "sonar-users";
+  private static final String DEFAULT_GROUP_UUID = "DEFAULT_GROUP_UUID";
+  @Mock
+  private DbSession dbSession;
+  @Mock
+  private DbClient dbClient;
+  @Mock
+  private UuidFactory uuidFactory;
+  @InjectMocks
+  private GroupService groupService;
+
+  @Rule
+  public MockitoRule rule = MockitoJUnit.rule();
+
+  @Before
+  public void setUp() {
+    mockNeededDaos();
+  }
+
+  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.externalGroupDao()).thenReturn(mock(ExternalGroupDao.class));
+    when(dbClient.groupDao()).thenReturn(mock(GroupDao.class));
+    when(dbClient.githubOrganizationGroupDao()).thenReturn(mock(GithubOrganizationGroupDao.class));
+  }
+
+  @Test
+  public void findGroup_whenGroupExists_returnsIt() {
+    GroupDto groupDto = mockGroupDto();
+
+    when(dbClient.groupDao().selectByName(dbSession, GROUP_NAME))
+      .thenReturn(Optional.of(groupDto));
+
+    assertThat(groupService.findGroup(dbSession, GROUP_NAME)).contains(groupDto);
+  }
+
+  @Test
+  public void findGroup_whenGroupDoesntExist_returnsEmtpyOptional() {
+    when(dbClient.groupDao().selectByName(dbSession, GROUP_NAME))
+      .thenReturn(Optional.empty());
+
+    assertThat(groupService.findGroup(dbSession, GROUP_NAME)).isEmpty();
+  }
+
+  @Test
+  public void findGroupByUuid_whenGroupExists_returnsIt() {
+    GroupDto groupDto = mockGroupDto();
+
+    when(dbClient.groupDao().selectByUuid(dbSession, GROUP_UUID))
+      .thenReturn(groupDto);
+
+    assertThat(groupService.findGroupByUuid(dbSession, GROUP_UUID)).contains(groupDto);
+  }
+
+  @Test
+  public void findGroupByUuid_whenGroupDoesntExist_returnsEmptyOptional() {
+    when(dbClient.groupDao().selectByUuid(dbSession, GROUP_UUID))
+      .thenReturn(null);
+
+    assertThat(groupService.findGroupByUuid(dbSession, GROUP_UUID)).isEmpty();
+  }
+
+  @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 updateGroup_updatesGroupNameAndDescription() {
+    GroupDto group = mockGroupDto();
+    GroupDto groupWithUpdatedName = mockGroupDto();
+    mockDefaultGroup();
+    when(dbClient.groupDao().update(dbSession, group)).thenReturn(groupWithUpdatedName);
+
+    groupService.updateGroup(dbSession, group, "new-name", "New Description");
+    verify(group).setName("new-name");
+    verify(groupWithUpdatedName).setDescription("New Description");
+    verify(dbClient.groupDao()).update(dbSession, group);
+    verify(dbClient.groupDao()).update(dbSession, groupWithUpdatedName);
+  }
+
+  @Test
+  public void updateGroup_updatesGroupName() {
+    GroupDto group = mockGroupDto();
+    mockDefaultGroup();
+
+    groupService.updateGroup(dbSession, group, "new-name");
+    verify(group).setName("new-name");
+    verify(dbClient.groupDao()).update(dbSession, group);
+  }
+
+  @Test
+  public void updateGroup_whenGroupIsDefault_throws() {
+    GroupDto defaultGroup = mockDefaultGroup();
+    when(dbClient.groupDao().selectByName(dbSession, DEFAULT_GROUP_NAME)).thenReturn(Optional.of(defaultGroup));
+
+    assertThatExceptionOfType(IllegalArgumentException.class)
+      .isThrownBy(() -> groupService.updateGroup(dbSession, defaultGroup, "new-name", "New Description"))
+      .withMessage("Default group 'sonar-users' cannot be used to perform this action");
+
+    assertThatExceptionOfType(IllegalArgumentException.class)
+      .isThrownBy(() -> groupService.updateGroup(dbSession, defaultGroup, "new-name"))
+      .withMessage("Default group 'sonar-users' cannot be used to perform this action");
+  }
+
+  @Test
+  public void updateGroup_whenGroupNameDoesntChange_succeedsWithDescription() {
+    GroupDto group = mockGroupDto();
+    mockDefaultGroup();
+
+    groupService.updateGroup(dbSession, group, group.getName(), "New Description");
+    verify(group).setDescription("New Description");
+    verify(dbClient.groupDao()).update(dbSession, group);
+  }
+
+  @Test
+  public void updateGroup_whenGroupNameDoesntChange_succeeds() {
+    GroupDto group = mockGroupDto();
+    mockDefaultGroup();
+
+    assertThatNoException()
+      .isThrownBy(() -> groupService.updateGroup(dbSession, group, group.getName()));
+
+    verify(dbClient.groupDao(), never()).update(dbSession, group);
+  }
+
+  @Test
+  public void updateGroup_whenGroupExist_throws() {
+    GroupDto group = mockGroupDto();
+    GroupDto group2 = mockGroupDto();
+    mockDefaultGroup();
+    String group2Name = GROUP_NAME + "2";
+
+    when(dbClient.groupDao().selectByName(dbSession, group2Name)).thenReturn(Optional.of(group2));
+
+    assertThatExceptionOfType(BadRequestException.class)
+      .isThrownBy(() -> groupService.updateGroup(dbSession, group, group2Name, "New Description"))
+      .withMessage("Group '" + group2Name + "' already exists");
+
+    assertThatExceptionOfType(BadRequestException.class)
+      .isThrownBy(() -> groupService.updateGroup(dbSession, group, group2Name))
+      .withMessage("Group '" + group2Name + "' already exists");
+  }
+
+  @Test
+  @UseDataProvider("invalidGroupNames")
+  public void updateGroup_whenGroupNameIsInvalid_throws(String groupName, String errorMessage) {
+    GroupDto group = mockGroupDto();
+    mockDefaultGroup();
+
+    assertThatExceptionOfType(BadRequestException.class)
+      .isThrownBy(() -> groupService.updateGroup(dbSession, group, groupName, "New Description"))
+      .withMessage(errorMessage);
+
+    assertThatExceptionOfType(BadRequestException.class)
+      .isThrownBy(() -> groupService.updateGroup(dbSession, group, groupName))
+      .withMessage(errorMessage);
+  }
+
+  @Test
+  public void createGroup_whenNameAndDescriptionIsProvided_createsGroup() {
+
+    when(uuidFactory.create()).thenReturn("1234");
+    groupService.createGroup(dbSession, "Name", "Description");
+
+    ArgumentCaptor<GroupDto> groupCaptor = ArgumentCaptor.forClass(GroupDto.class);
+    verify(dbClient.groupDao()).insert(eq(dbSession), groupCaptor.capture());
+    GroupDto createdGroup = groupCaptor.getValue();
+    assertThat(createdGroup.getName()).isEqualTo("Name");
+    assertThat(createdGroup.getDescription()).isEqualTo("Description");
+    assertThat(createdGroup.getUuid()).isEqualTo("1234");
+  }
+
+  @Test
+  public void createGroup_whenGroupExist_throws() {
+    GroupDto group = mockGroupDto();
+
+    when(dbClient.groupDao().selectByName(dbSession, GROUP_NAME)).thenReturn(Optional.of(group));
+
+    assertThatExceptionOfType(BadRequestException.class)
+      .isThrownBy(() -> groupService.createGroup(dbSession, GROUP_NAME, "New Description"))
+      .withMessage("Group '" + GROUP_NAME + "' already exists");
+
+  }
+
+  @Test
+  @UseDataProvider("invalidGroupNames")
+  public void createGroup_whenGroupNameIsInvalid_throws(String groupName, String errorMessage) {
+    mockDefaultGroup();
+
+    assertThatExceptionOfType(BadRequestException.class)
+      .isThrownBy(() -> groupService.createGroup(dbSession, groupName, "Description"))
+      .withMessage(errorMessage);
+
+  }
+
+  @DataProvider
+  public static Object[][] invalidGroupNames() {
+    return new Object[][] {
+      {"", "Group name cannot be empty"},
+      {randomAlphanumeric(256), "Group name cannot be longer than 255 characters"},
+      {"Anyone", "Anyone group cannot be used"},
+    };
+  }
+
+  private static GroupDto mockGroupDto() {
+    GroupDto groupDto = mock(GroupDto.class);
+    when(groupDto.getName()).thenReturn(GROUP_NAME);
+    when(groupDto.getUuid()).thenReturn(GROUP_UUID);
+    return groupDto;
+  }
+
+  private GroupDto mockDefaultGroup() {
+    GroupDto defaultGroup = mock(GroupDto.class);
+    when(defaultGroup.getName()).thenReturn(DEFAULT_GROUP_NAME);
+    when(defaultGroup.getUuid()).thenReturn(DEFAULT_GROUP_UUID);
+    when(dbClient.groupDao().selectByName(dbSession, DEFAULT_GROUP_NAME)).thenReturn(Optional.of(defaultGroup));
+    return defaultGroup;
+  }
+
+  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());
+    verify(dbClient.githubOrganizationGroupDao(), never()).deleteByGroupUuid(dbSession, groupDto.getUuid());
+  }
+
+  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.externalGroupDao()).deleteByGroupUuid(dbSession, groupDto.getUuid());
+    verify(dbClient.groupDao()).deleteByUuid(dbSession, groupDto.getUuid(), groupDto.getName());
+    verify(dbClient.githubOrganizationGroupDao()).deleteByGroupUuid(dbSession, groupDto.getUuid());
+  }
+}
index 9e5c9aa684def8c7c5d024a15268d042169ae3c5..7c99e450da17c89fa08840d61156e44fdbc34246 100644 (file)
@@ -27,6 +27,9 @@ public class WebApiEndpoints {
   public static final String USERS_MANAGEMENT_DOMAIN = "/users-management";
   public static final String USER_ENDPOINT = USERS_MANAGEMENT_DOMAIN + "/users";
   public static final String JSON_MERGE_PATCH_CONTENT_TYPE = "application/merge-patch+json";
+  public static final String AUTHORIZATIONS_DOMAIN = "/authorizations";
+
+  public static final String GROUPS_ENDPOINT = AUTHORIZATIONS_DOMAIN + "/groups";
 
   private WebApiEndpoints() {
   }
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/controller/DefaultGroupController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/controller/DefaultGroupController.java
new file mode 100644 (file)
index 0000000..49a4e4f
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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.v2.api.group.controller;
+
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.user.GroupDto;
+import org.sonar.server.common.group.service.GroupService;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.v2.api.group.response.RestGroupResponse;
+
+public class DefaultGroupController implements GroupController {
+
+  private static final String GROUP_NOT_FOUND_MESSAGE = "Group '%s' not found";
+  private final GroupService groupService;
+  private final DbClient dbClient;
+  private final UserSession userSession;
+
+  public DefaultGroupController(GroupService groupService, DbClient dbClient, UserSession userSession) {
+    this.groupService = groupService;
+    this.dbClient = dbClient;
+    this.userSession = userSession;
+  }
+
+  @Override
+  public RestGroupResponse fetchGroup(String id) {
+    userSession.checkLoggedIn().checkIsSystemAdministrator();
+    try (DbSession session = dbClient.openSession(false)) {
+      return groupService.findGroupByUuid(session, id)
+        .map(DefaultGroupController::groupDtoToResponse)
+        .orElseThrow(() -> new NotFoundException(String.format(GROUP_NOT_FOUND_MESSAGE, id)));
+    }
+  }
+
+  private static RestGroupResponse groupDtoToResponse(GroupDto group) {
+    return new RestGroupResponse(group.getUuid(), group.getName(), group.getDescription());
+  }
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/controller/GroupController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/controller/GroupController.java
new file mode 100644 (file)
index 0000000..f889fc8
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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.v2.api.group.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import org.sonar.server.v2.api.group.response.RestGroupResponse;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+import static org.sonar.server.v2.WebApiEndpoints.GROUPS_ENDPOINT;
+
+@RequestMapping(GROUPS_ENDPOINT)
+@RestController
+public interface GroupController {
+
+  @GetMapping(path = "/{id}")
+  @ResponseStatus(HttpStatus.OK)
+  @Operation(summary = "Fetch a single group", description = "Fetch a single group.")
+  RestGroupResponse fetchGroup(@PathVariable("id") @Parameter(description = "The id of the group to fetch.", required = true, in = ParameterIn.PATH) String id);
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/controller/package-info.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/controller/package-info.java
new file mode 100644 (file)
index 0000000..44abc95
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.v2.api.group.controller;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/response/RestGroupResponse.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/response/RestGroupResponse.java
new file mode 100644 (file)
index 0000000..6a4bdd3
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * 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.v2.api.group.response;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import javax.annotation.Nullable;
+
+public record RestGroupResponse(
+  @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+  String id,
+  String name,
+  @Nullable
+  String description) {
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/response/package-info.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/response/package-info.java
new file mode 100644 (file)
index 0000000..e70cba3
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.v2.api.group.response;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index 9231cc10c6b5481a54ef41bc3d4f0fba982a9ac9..fa61ea447ccaad935e39ca5787ba6b4a389245e9 100644 (file)
@@ -20,6 +20,8 @@
 package org.sonar.server.v2.config;
 
 import javax.annotation.Nullable;
+import org.sonar.db.DbClient;
+import org.sonar.server.common.group.service.GroupService;
 import org.sonar.server.common.health.CeStatusNodeCheck;
 import org.sonar.server.common.health.DbConnectionNodeCheck;
 import org.sonar.server.common.health.EsStatusNodeCheck;
@@ -31,6 +33,8 @@ import org.sonar.server.health.HealthChecker;
 import org.sonar.server.platform.NodeInformation;
 import org.sonar.server.user.SystemPasscode;
 import org.sonar.server.user.UserSession;
+import org.sonar.server.v2.api.group.controller.DefaultGroupController;
+import org.sonar.server.v2.api.group.controller.GroupController;
 import org.sonar.server.v2.api.system.controller.DefaultLivenessController;
 import org.sonar.server.v2.api.system.controller.HealthController;
 import org.sonar.server.v2.api.system.controller.LivenessController;
@@ -75,4 +79,9 @@ public class PlatformLevel4WebConfig {
     return new DefaultUserController(userSession, userService, usersSearchResponseGenerator);
   }
 
+  @Bean
+  public GroupController groupController(GroupService groupService, DbClient dbClient, UserSession userSession) {
+    return new DefaultGroupController(groupService, dbClient, userSession);
+  }
+
 }
diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/group/controller/DefaultGroupControllerTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/group/controller/DefaultGroupControllerTest.java
new file mode 100644 (file)
index 0000000..387b6da
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * 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.v2.api.group.controller;
+
+import java.util.Optional;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.user.GroupDto;
+import org.sonar.server.common.group.service.GroupService;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.v2.api.ControllerTester;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.v2.WebApiEndpoints.GROUPS_ENDPOINT;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+public class DefaultGroupControllerTest {
+
+  private static final String GROUP_UUID = "1234";
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+
+  private final GroupService groupService = mock(GroupService.class);
+  private final DbClient dbClient = mock(DbClient.class);
+
+  private final DbSession dbSession = mock(DbSession.class);
+
+  private final MockMvc mockMvc = ControllerTester.getMockMvc(new DefaultGroupController(groupService, dbClient, userSession));
+
+  @Before
+  public void setUp() {
+    when(dbClient.openSession(false)).thenReturn(dbSession);
+  }
+
+  @Test
+  public void fetchGroup_whenGroupExists_returnsTheGroup() throws Exception {
+
+    GroupDto groupDto = new GroupDto().setUuid(GROUP_UUID).setName("name").setDescription("description");
+
+    when(groupService.findGroupByUuid(dbSession, GROUP_UUID)).thenReturn(Optional.of(groupDto));
+
+    userSession.logIn().setSystemAdministrator();
+    mockMvc.perform(get(GROUPS_ENDPOINT + "/" + GROUP_UUID))
+      .andExpectAll(
+        status().isOk(),
+        content().json("""
+          {
+            "id": "1234",
+            "name": "name",
+            "description": "description"
+          }
+          """));
+  }
+
+  @Test
+  public void fetchGroup_whenCallerIsNotAdmin_shouldReturnForbidden() throws Exception {
+    userSession.logIn().setNonSystemAdministrator();
+    mockMvc.perform(
+      get(GROUPS_ENDPOINT + "/" + GROUP_UUID))
+      .andExpectAll(
+        status().isForbidden(),
+        content().json("{\"message\":\"Insufficient privileges\"}"));
+  }
+
+  @Test
+  public void fetchGroup_whenGroupDoesntExist_shouldReturnNotFound() throws Exception {
+    userSession.logIn().setSystemAdministrator();
+    when(groupService.findGroupByUuid(dbSession, GROUP_UUID)).thenReturn(Optional.empty());
+    mockMvc.perform(
+      get(GROUPS_ENDPOINT + "/" + GROUP_UUID)
+        .content("{}"))
+      .andExpectAll(
+        status().isNotFound(),
+        content().json("{\"message\":\"Group '1234' not found\"}"));
+  }
+
+}
index 3541a23cfb00cd3497b1d78a83d42e73058bcda4..225e2478031fc0b6cba003d5bcb191e3bbe9bf77 100644 (file)
@@ -28,6 +28,7 @@ import org.sonar.api.utils.System2;
 import org.sonar.core.util.SequenceUuidFactory;
 import org.sonar.db.DbTester;
 import org.sonar.db.user.GroupDto;
+import org.sonar.server.common.group.service.GroupService;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.ServerException;
index cba6d341edbe5d3280b903bcbf3fcf1ddf797a9f..f8d2e2eee3673934e46963701bda567bd6e0fa03 100644 (file)
@@ -37,6 +37,7 @@ import org.sonar.db.qualitygate.QualityGateDto;
 import org.sonar.db.qualityprofile.QProfileDto;
 import org.sonar.db.user.GroupDto;
 import org.sonar.db.user.UserDto;
+import org.sonar.server.common.group.service.GroupService;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.management.ManagedInstanceService;
index a355dbfd16490a22da82cc7caae482d76f087e32..b9666421dbf62e0b8db69f5f44a138f8e161e30f 100644 (file)
@@ -29,6 +29,7 @@ import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.user.ExternalGroupDto;
 import org.sonar.db.user.GroupDto;
+import org.sonar.server.common.group.service.GroupService;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
index 787af00f823da77464a200380370dbf858d8dd34..f82cbaee95510f1a0265477b107afc4625305bab 100644 (file)
@@ -28,6 +28,7 @@ import org.sonar.core.util.UuidFactoryImpl;
 import org.sonar.db.DbTester;
 import org.sonar.db.user.GroupDto;
 import org.sonar.db.user.UserDto;
+import org.sonar.server.common.group.service.GroupService;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
index a728d4704a98d17dff4df7003c407304df61c8a0..c6d367e49769964e812a036fa896f93153884872 100644 (file)
@@ -27,6 +27,7 @@ import org.sonar.api.server.ws.WebService.NewController;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.user.GroupDto;
+import org.sonar.server.common.group.service.GroupService;
 import org.sonar.server.common.management.ManagedInstanceChecker;
 import org.sonar.server.user.UserSession;
 import org.sonarqube.ws.UserGroups;
index bc16c54cca6cc893ecb862bd518779c89d5b6c6f..f0f67ac5fe97ec1eb06eb2f3991c3cba148a477e 100644 (file)
@@ -29,6 +29,7 @@ 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.server.common.group.service.GroupService;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.management.ManagedInstanceService;
index dc5befd6bb2e968e978941b4e858a104a1bfab89..989615b13dc515b91ba2084b9f918fa45dcd20b4 100644 (file)
@@ -26,6 +26,7 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.user.ExternalGroupDto;
 import org.sonar.db.user.GroupDto;
+import org.sonar.server.common.group.service.GroupService;
 
 public class ExternalGroupService {
 
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
deleted file mode 100644 (file)
index 1dc200f..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * 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 javax.annotation.Nullable;
-import org.sonar.api.security.DefaultGroups;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.user.UserGroupValidation;
-import org.sonar.core.util.UuidFactory;
-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.server.exceptions.BadRequestException;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static org.sonar.server.exceptions.BadRequestException.checkRequest;
-
-@ServerSide
-public class GroupService {
-
-  private final DbClient dbClient;
-  private final UuidFactory uuidFactory;
-
-  public GroupService(DbClient dbClient, UuidFactory uuidFactory) {
-    this.dbClient = dbClient;
-    this.uuidFactory = uuidFactory;
-  }
-
-  public Optional<GroupDto> findGroup(DbSession dbSession, String groupName) {
-    return dbClient.groupDao().selectByName(dbSession, 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);
-    removeExternalGroupMapping(dbSession, group);
-    removeGithubOrganizationGroup(dbSession, group);
-
-    removeGroup(dbSession, group);
-  }
-
-  public GroupDto updateGroup(DbSession dbSession, GroupDto group, @Nullable String newName) {
-    checkGroupIsNotDefault(dbSession, group);
-    return updateName(dbSession, group, newName);
-  }
-
-  public GroupDto updateGroup(DbSession dbSession, GroupDto group, @Nullable String newName, @Nullable String newDescription) {
-    checkGroupIsNotDefault(dbSession, group);
-    GroupDto withUpdatedName = updateName(dbSession, group, newName);
-    return updateDescription(dbSession, withUpdatedName, newDescription);
-  }
-
-  public GroupDto createGroup(DbSession dbSession, String name, @Nullable String description) {
-    validateGroupName(name);
-    checkNameDoesNotExist(dbSession, name);
-
-    GroupDto group = new GroupDto()
-      .setUuid(uuidFactory.create())
-      .setName(name)
-      .setDescription(description);
-    return dbClient.groupDao().insert(dbSession, group);
-  }
-
-  private GroupDto updateName(DbSession dbSession, GroupDto group, @Nullable String newName) {
-    if (newName != null && !newName.equals(group.getName())) {
-      validateGroupName(newName);
-      checkNameDoesNotExist(dbSession, newName);
-      group.setName(newName);
-      return dbClient.groupDao().update(dbSession, group);
-    }
-    return group;
-  }
-
-  private static void validateGroupName(String name) {
-    try {
-      UserGroupValidation.validateGroupName(name);
-    } catch (IllegalArgumentException e) {
-      BadRequestException.throwBadRequestException(e.getMessage());
-    }
-  }
-
-  private void checkNameDoesNotExist(DbSession dbSession, String name) {
-    // There is no database constraint on column groups.name
-    // because MySQL cannot create a unique index
-    // on a UTF-8 VARCHAR larger than 255 characters on InnoDB
-    checkRequest(!dbClient.groupDao().selectByName(dbSession, name).isPresent(), "Group '%s' already exists", name);
-  }
-
-  private GroupDto updateDescription(DbSession dbSession, GroupDto group, @Nullable String newDescription) {
-    if (newDescription != null) {
-      group.setDescription(newDescription);
-      return dbClient.groupDao().update(dbSession, group);
-    }
-    return group;
-  }
-
-  private 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 removeExternalGroupMapping(DbSession dbSession, GroupDto group) {
-    dbClient.externalGroupDao().deleteByGroupUuid(dbSession, group.getUuid());
-  }
-  private void removeGithubOrganizationGroup(DbSession dbSession, GroupDto group) {
-    dbClient.githubOrganizationGroupDao().deleteByGroupUuid(dbSession, group.getUuid());
-  }
-
-  private void removeGroup(DbSession dbSession, GroupDto group) {
-    dbClient.groupDao().deleteByUuid(dbSession, group.getUuid(), group.getName());
-  }
-
-}
index 8113fa13afd27e1a17ff95bbc9589953dec81b60..7310c11302b342e66a30751704d3c6d01b75b48b 100644 (file)
@@ -28,6 +28,7 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.user.GroupDto;
 import org.sonar.db.user.UserMembershipQuery;
+import org.sonar.server.common.group.service.GroupService;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.common.management.ManagedInstanceChecker;
 import org.sonar.server.user.UserSession;
index 22ea5abe0bfd1d98f3d121c184f655d31e5fe4f8..9f92f4881cdacee62f6541714f795e3bb1927987 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.usergroups.ws;
 
 import org.sonar.core.platform.Module;
+import org.sonar.server.common.group.service.GroupService;
 import org.sonar.server.common.management.ManagedInstanceChecker;
 
 public class UserGroupsModule extends Module {
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
deleted file mode 100644 (file)
index 998a509..0000000
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * 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 com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.Optional;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-import org.sonar.api.security.DefaultGroups;
-import org.sonar.core.util.UuidFactory;
-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.provisioning.GithubOrganizationGroupDao;
-import org.sonar.db.qualitygate.QualityGateGroupPermissionsDao;
-import org.sonar.db.qualityprofile.QProfileEditGroupsDao;
-import org.sonar.db.scim.ScimGroupDao;
-import org.sonar.db.user.ExternalGroupDao;
-import org.sonar.db.user.GroupDao;
-import org.sonar.db.user.GroupDto;
-import org.sonar.db.user.RoleDao;
-import org.sonar.db.user.UserGroupDao;
-import org.sonar.server.exceptions.BadRequestException;
-
-import static java.lang.String.format;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatNoException;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
-import static org.mockito.ArgumentMatchers.eq;
-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(DataProviderRunner.class)
-public class GroupServiceTest {
-
-  private static final String GROUP_NAME = "GROUP_NAME";
-  private static final String GROUP_UUID = "GROUP_UUID";
-  private static final String DEFAULT_GROUP_NAME = "sonar-users";
-  private static final String DEFAULT_GROUP_UUID = "DEFAULT_GROUP_UUID";
-  @Mock
-  private DbSession dbSession;
-  @Mock
-  private DbClient dbClient;
-  @Mock
-  private UuidFactory uuidFactory;
-  @InjectMocks
-  private GroupService groupService;
-
-  @Rule
-  public MockitoRule rule = MockitoJUnit.rule();
-
-  @Before
-  public void setUp() {
-    mockNeededDaos();
-  }
-
-  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.externalGroupDao()).thenReturn(mock(ExternalGroupDao.class));
-    when(dbClient.groupDao()).thenReturn(mock(GroupDao.class));
-    when(dbClient.githubOrganizationGroupDao()).thenReturn(mock(GithubOrganizationGroupDao.class));
-  }
-
-  @Test
-  public void findGroupDtoOrThrow_whenGroupExists_returnsIt() {
-    GroupDto groupDto = mockGroupDto();
-
-    when(dbClient.groupDao().selectByName(dbSession, GROUP_NAME))
-      .thenReturn(Optional.of(groupDto));
-
-    assertThat(groupService.findGroup(dbSession, GROUP_NAME)).contains(groupDto);
-  }
-
-  @Test
-  public void findGroupDtoOrThrow_whenGroupDoesntExist_throw() {
-    when(dbClient.groupDao().selectByName(dbSession, GROUP_NAME))
-      .thenReturn(Optional.empty());
-
-    assertThat(groupService.findGroup(dbSession, GROUP_NAME)).isEmpty();
-  }
-
-  @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 updateGroup_updatesGroupNameAndDescription() {
-    GroupDto group = mockGroupDto();
-    GroupDto groupWithUpdatedName = mockGroupDto();
-    mockDefaultGroup();
-    when(dbClient.groupDao().update(dbSession, group)).thenReturn(groupWithUpdatedName);
-
-    groupService.updateGroup(dbSession, group, "new-name", "New Description");
-    verify(group).setName("new-name");
-    verify(groupWithUpdatedName).setDescription("New Description");
-    verify(dbClient.groupDao()).update(dbSession, group);
-    verify(dbClient.groupDao()).update(dbSession, groupWithUpdatedName);
-  }
-
-  @Test
-  public void updateGroup_updatesGroupName() {
-    GroupDto group = mockGroupDto();
-    mockDefaultGroup();
-
-    groupService.updateGroup(dbSession, group, "new-name");
-    verify(group).setName("new-name");
-    verify(dbClient.groupDao()).update(dbSession, group);
-  }
-
-  @Test
-  public void updateGroup_whenGroupIsDefault_throws() {
-    GroupDto defaultGroup = mockDefaultGroup();
-    when(dbClient.groupDao().selectByName(dbSession, DEFAULT_GROUP_NAME)).thenReturn(Optional.of(defaultGroup));
-
-    assertThatExceptionOfType(IllegalArgumentException.class)
-      .isThrownBy(() -> groupService.updateGroup(dbSession, defaultGroup, "new-name", "New Description"))
-      .withMessage("Default group 'sonar-users' cannot be used to perform this action");
-
-    assertThatExceptionOfType(IllegalArgumentException.class)
-      .isThrownBy(() -> groupService.updateGroup(dbSession, defaultGroup, "new-name"))
-      .withMessage("Default group 'sonar-users' cannot be used to perform this action");
-  }
-
-  @Test
-  public void updateGroup_whenGroupNameDoesntChange_succeedsWithDescription() {
-    GroupDto group = mockGroupDto();
-    mockDefaultGroup();
-
-    groupService.updateGroup(dbSession, group, group.getName(), "New Description");
-    verify(group).setDescription("New Description");
-    verify(dbClient.groupDao()).update(dbSession, group);
-  }
-
-  @Test
-  public void updateGroup_whenGroupNameDoesntChange_succeeds() {
-    GroupDto group = mockGroupDto();
-    mockDefaultGroup();
-
-    assertThatNoException()
-      .isThrownBy(() -> groupService.updateGroup(dbSession, group, group.getName()));
-
-    verify(dbClient.groupDao(), never()).update(dbSession, group);
-  }
-
-  @Test
-  public void updateGroup_whenGroupExist_throws() {
-    GroupDto group = mockGroupDto();
-    GroupDto group2 = mockGroupDto();
-    mockDefaultGroup();
-    String group2Name = GROUP_NAME + "2";
-
-    when(dbClient.groupDao().selectByName(dbSession, group2Name)).thenReturn(Optional.of(group2));
-
-    assertThatExceptionOfType(BadRequestException.class)
-      .isThrownBy(() -> groupService.updateGroup(dbSession, group, group2Name, "New Description"))
-      .withMessage("Group '" + group2Name + "' already exists");
-
-    assertThatExceptionOfType(BadRequestException.class)
-      .isThrownBy(() -> groupService.updateGroup(dbSession, group, group2Name))
-      .withMessage("Group '" + group2Name + "' already exists");
-  }
-
-  @Test
-  @UseDataProvider("invalidGroupNames")
-  public void updateGroup_whenGroupNameIsInvalid_throws(String groupName, String errorMessage) {
-    GroupDto group = mockGroupDto();
-    mockDefaultGroup();
-
-    assertThatExceptionOfType(BadRequestException.class)
-      .isThrownBy(() -> groupService.updateGroup(dbSession, group, groupName, "New Description"))
-      .withMessage(errorMessage);
-
-    assertThatExceptionOfType(BadRequestException.class)
-      .isThrownBy(() -> groupService.updateGroup(dbSession, group, groupName))
-      .withMessage(errorMessage);
-  }
-
-  @Test
-  public void createGroup_whenNameAndDescriptionIsProvided_createsGroup() {
-
-    when(uuidFactory.create()).thenReturn("1234");
-    groupService.createGroup(dbSession, "Name", "Description");
-
-    ArgumentCaptor<GroupDto> groupCaptor = ArgumentCaptor.forClass(GroupDto.class);
-    verify(dbClient.groupDao()).insert(eq(dbSession), groupCaptor.capture());
-    GroupDto createdGroup = groupCaptor.getValue();
-    assertThat(createdGroup.getName()).isEqualTo("Name");
-    assertThat(createdGroup.getDescription()).isEqualTo("Description");
-    assertThat(createdGroup.getUuid()).isEqualTo("1234");
-  }
-
-  @Test
-  public void createGroup_whenGroupExist_throws() {
-    GroupDto group = mockGroupDto();
-
-    when(dbClient.groupDao().selectByName(dbSession, GROUP_NAME)).thenReturn(Optional.of(group));
-
-    assertThatExceptionOfType(BadRequestException.class)
-      .isThrownBy(() -> groupService.createGroup(dbSession, GROUP_NAME, "New Description"))
-      .withMessage("Group '" + GROUP_NAME + "' already exists");
-
-  }
-
-  @Test
-  @UseDataProvider("invalidGroupNames")
-  public void createGroup_whenGroupNameIsInvalid_throws(String groupName, String errorMessage) {
-    mockDefaultGroup();
-
-    assertThatExceptionOfType(BadRequestException.class)
-      .isThrownBy(() -> groupService.createGroup(dbSession, groupName, "Description"))
-      .withMessage(errorMessage);
-
-  }
-
-  @DataProvider
-  public static Object[][] invalidGroupNames() {
-    return new Object[][] {
-      {"", "Group name cannot be empty"},
-      {randomAlphanumeric(256), "Group name cannot be longer than 255 characters"},
-      {"Anyone", "Anyone group cannot be used"},
-    };
-  }
-
-  private static GroupDto mockGroupDto() {
-    GroupDto groupDto = mock(GroupDto.class);
-    when(groupDto.getName()).thenReturn(GROUP_NAME);
-    when(groupDto.getUuid()).thenReturn(GROUP_UUID);
-    return groupDto;
-  }
-
-  private GroupDto mockDefaultGroup() {
-    GroupDto defaultGroup = mock(GroupDto.class);
-    when(defaultGroup.getName()).thenReturn(DEFAULT_GROUP_NAME);
-    when(defaultGroup.getUuid()).thenReturn(DEFAULT_GROUP_UUID);
-    when(dbClient.groupDao().selectByName(dbSession, DEFAULT_GROUP_NAME)).thenReturn(Optional.of(defaultGroup));
-    return defaultGroup;
-  }
-
-  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());
-    verify(dbClient.githubOrganizationGroupDao(), never()).deleteByGroupUuid(dbSession, groupDto.getUuid());
-  }
-
-  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.externalGroupDao()).deleteByGroupUuid(dbSession, groupDto.getUuid());
-    verify(dbClient.groupDao()).deleteByUuid(dbSession, groupDto.getUuid(), groupDto.getName());
-    verify(dbClient.githubOrganizationGroupDao()).deleteByGroupUuid(dbSession, groupDto.getUuid());
-  }
-}