]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19085 Create and update GitHub groups
authorAntoine Vigneau <antoine.vigneau@sonarsource.com>
Tue, 2 May 2023 15:22:50 +0000 (17:22 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 11 May 2023 20:03:14 +0000 (20:03 +0000)
server/sonar-db-dao/src/it/java/org/sonar/db/user/ExternalGroupDaoIT.java
server/sonar-db-dao/src/main/java/org/sonar/db/user/ExternalGroupDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/user/ExternalGroupMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/user/GroupQuery.java
server/sonar-db-dao/src/main/resources/org/sonar/db/user/ExternalGroupMapper.xml
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/ExternalGroupServiceIT.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/ExternalGroupService.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/GroupRegistration.java [new file with mode: 0644]

index 26d47127977454fe9c0790aa29a6fdee1f73172d..6de7ba2c279c62f67df94fa46da115ccae56d948 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.db.user;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.db.DbSession;
@@ -30,6 +31,10 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 public class ExternalGroupDaoIT {
 
+  private static final String GROUP_UUID = "uuid";
+  private static final String EXTERNAL_ID = "external_id";
+  private static final String EXTERNAL_IDENTITY_PROVIDER = "external_identity_provider";
+
   @Rule
   public final DbTester db = DbTester.create();
 
@@ -41,16 +46,27 @@ public class ExternalGroupDaoIT {
 
   @Test
   public void insert_savesExternalGroup() {
-    GroupDto localGroup = insertGroup("12345");
+    GroupDto localGroup = insertGroup(GROUP_UUID);
     insertGroup("67689");
-    ExternalGroupDto externalGroupDto = externalGroup("12345", "providerId");
+    ExternalGroupDto externalGroupDto = externalGroup(GROUP_UUID, EXTERNAL_IDENTITY_PROVIDER);
     underTest.insert(dbSession, externalGroupDto);
-    List<ExternalGroupDto> savedGroups = underTest.selectByIdentityProvider(dbSession, "providerId");
+    List<ExternalGroupDto> savedGroups = underTest.selectByIdentityProvider(dbSession, EXTERNAL_IDENTITY_PROVIDER);
     assertThat(savedGroups)
       .hasSize(1)
       .contains(createExternalGroupDto(localGroup.getName(), externalGroupDto));
   }
 
+  @Test
+  public void selectByGroupUuid_shouldReturnExternalGroup() {
+    ExternalGroupDto expectedExternalGroupDto = new ExternalGroupDto(GROUP_UUID, EXTERNAL_ID, EXTERNAL_IDENTITY_PROVIDER);
+    underTest.insert(dbSession, expectedExternalGroupDto);
+
+    Optional<ExternalGroupDto> actualExternalGroupDto = underTest.selectByGroupUuid(dbSession, GROUP_UUID);
+
+    assertThat(actualExternalGroupDto).isPresent();
+    compareExpectedAndActualExternalGroupDto(expectedExternalGroupDto, actualExternalGroupDto.get());
+  }
+
   @Test
   public void selectByIdentityProvider_returnOnlyGroupForTheIdentityProvider() {
     List<ExternalGroupDto> expectedGroups = createAndInsertExternalGroupDtos("provider1", 3);
@@ -59,6 +75,25 @@ public class ExternalGroupDaoIT {
     assertThat(savedGroup).containsExactlyInAnyOrderElementsOf(expectedGroups);
   }
 
+  @Test
+  public void selectByExternalIdAndIdentityProvider_shouldReturnOnlyMatchingExternalGroup() {
+    ExternalGroupDto expectedExternalGroupDto = new ExternalGroupDto(GROUP_UUID, EXTERNAL_ID, EXTERNAL_IDENTITY_PROVIDER);
+    underTest.insert(dbSession, expectedExternalGroupDto);
+    underTest.insert(dbSession, new ExternalGroupDto(GROUP_UUID + "1", EXTERNAL_ID, "another_external_identity_provider"));
+    underTest.insert(dbSession, new ExternalGroupDto(GROUP_UUID + "2", "another_external_id", EXTERNAL_IDENTITY_PROVIDER));
+    underTest.insert(dbSession, new ExternalGroupDto(GROUP_UUID + "3", "whatever", "whatever"));
+
+    Optional<ExternalGroupDto> actualExternalGroupDto = underTest.selectByExternalIdAndIdentityProvider(dbSession, EXTERNAL_ID, EXTERNAL_IDENTITY_PROVIDER);
+
+    compareExpectedAndActualExternalGroupDto(expectedExternalGroupDto, actualExternalGroupDto.get());
+  }
+
+  private void compareExpectedAndActualExternalGroupDto(ExternalGroupDto expectedExternalGroupDto, ExternalGroupDto actualExternalGroupDto) {
+    assertThat(actualExternalGroupDto)
+      .usingRecursiveComparison()
+      .isEqualTo(expectedExternalGroupDto);
+  }
+
   @Test
   public void deleteByGroupUuid_deletesTheGroup() {
     List<ExternalGroupDto> insertedGroups = createAndInsertExternalGroupDtos("provider1", 3);
index 9980aaa60ceffbc24ccb0d38bc52fcfc50c304fc..743236638fb4aa05a142567a1a5c38532088424d 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.db.user;
 
 import java.util.List;
+import java.util.Optional;
 import org.sonar.db.Dao;
 import org.sonar.db.DbSession;
 
@@ -29,6 +30,10 @@ public class ExternalGroupDao implements Dao {
     mapper(dbSession).insert(externalGroupDto);
   }
 
+  public Optional<ExternalGroupDto> selectByGroupUuid(DbSession dbSession, String groupUuid) {
+    return mapper(dbSession).selectByGroupUuid(groupUuid);
+  }
+
   public List<ExternalGroupDto> selectByIdentityProvider(DbSession dbSession, String identityProvider) {
     return mapper(dbSession).selectByIdentityProvider(identityProvider);
   }
@@ -37,6 +42,10 @@ public class ExternalGroupDao implements Dao {
     mapper(dbSession).deleteByGroupUuid(groupUuid);
   }
 
+  public Optional<ExternalGroupDto> selectByExternalIdAndIdentityProvider(DbSession dbSession, String externalId, String identityProvider) {
+    return mapper(dbSession).selectByExternalIdAndIdentityProvider(externalId, identityProvider);
+  }
+
   private static ExternalGroupMapper mapper(DbSession session) {
     return session.getMapper(ExternalGroupMapper.class);
   }
index b456be15a66d84a5ea41ef4e5006b6af229b7167..908aae7664f9351f12c14c02815db0d7e9c9a9e1 100644 (file)
 package org.sonar.db.user;
 
 import java.util.List;
+import java.util.Optional;
 import org.apache.ibatis.annotations.Param;
 
 public interface ExternalGroupMapper {
 
   void insert(ExternalGroupDto externalGroupDto);
 
-  List<ExternalGroupDto> selectByIdentityProvider(String identityProvider);
+  Optional<ExternalGroupDto> selectByGroupUuid(@Param("groupUuid") String groupUuid);
 
-  void deleteByGroupUuid(@Param("groupUuid") String groupUuid);
+  List<ExternalGroupDto> selectByIdentityProvider(@Param("identityProvider") String identityProvider);
 
+  Optional<ExternalGroupDto> selectByExternalIdAndIdentityProvider(@Param("externalId") String externalId, @Param("identityProvider") String identityProvider);
 
+  void deleteByGroupUuid(@Param("groupUuid") String groupUuid);
 }
index a8cf01456dcdb5ee8561a69347b33e87afa9469b..d169534193bd795f01551bc2e7a0efb9f1fca20c 100644 (file)
@@ -30,7 +30,7 @@ public class GroupQuery {
   private final String searchText;
   private final String isManagedSqlClause;
 
-  private GroupQuery(@Nullable String searchText, @Nullable String isManagedSqlClause) {
+  GroupQuery(@Nullable String searchText, @Nullable String isManagedSqlClause) {
     this.searchText = searchTextToSearchTextSql(searchText);
     this.isManagedSqlClause = isManagedSqlClause;
   }
index be2db7475b49c40026f41ea2c1d18811ee13a06a..a63497ece33e2518790a05c747430dd866e49024 100644 (file)
@@ -4,6 +4,12 @@
 
 <mapper namespace="org.sonar.db.user.ExternalGroupMapper">
 
+  <sql id="externalGroupColumns">
+    eg.group_uuid as groupUuid,
+    eg.external_group_id as external_id,
+    eg.external_identity_provider as externalIdentityProvider
+  </sql>
+
   <insert id="insert" useGeneratedKeys="false" parameterType="ExternalGroup">
     insert into external_groups (
       group_uuid,
     )
   </insert>
 
+    <select id="selectByGroupUuid" parameterType="String" resultType="ExternalGroup">
+    SELECT
+      <include refid="externalGroupColumns"/>
+    FROM external_groups eg
+    WHERE eg.group_uuid=#{groupUuid,jdbcType=VARCHAR}
+  </select>
+
   <select id="selectByIdentityProvider" parameterType="String" resultType="ExternalGroup">
     SELECT
-      eg.group_uuid as groupUuid,
-      eg.external_group_id as external_id,
-      eg.external_identity_provider as externalIdentityProvider,
+      <include refid="externalGroupColumns"/>,
       g.name as name
     FROM external_groups eg
     LEFT JOIN groups g ON eg.group_uuid = g.uuid
     WHERE eg.external_identity_provider=#{identityProvider,jdbcType=VARCHAR}
   </select>
 
+    <select id="selectByExternalIdAndIdentityProvider" parameterType="String" resultType="ExternalGroup">
+    SELECT
+      <include refid="externalGroupColumns"/>
+    FROM external_groups eg
+    WHERE eg.external_group_id=#{externalId,jdbcType=VARCHAR}
+    AND eg.external_identity_provider=#{identityProvider,jdbcType=VARCHAR}
+  </select>
+
   <delete id="deleteByGroupUuid" parameterType="String">
     delete from external_groups where group_uuid = #{groupUuid, jdbcType=VARCHAR}
   </delete>
index 45a2815630bd2d3a3b7eb9f1bdfe5d4d88f41947..4513d2608e3d26f0e47dcc9a10013c70f9fd6f6a 100644 (file)
@@ -163,6 +163,12 @@ public class UserDbTester {
     db.commit();
   }
 
+  public ExternalGroupDto insertExternalGroup(ExternalGroupDto dto) {
+    db.getDbClient().externalGroupDao().insert(db.getSession(), dto);
+    db.commit();
+    return dto;
+  }
+
   public ScimGroupDto insertScimGroup(GroupDto dto) {
     ScimGroupDto result = db.getDbClient().scimGroupDao().enableScimForGroup(db.getSession(), dto.getUuid());
     db.commit();
@@ -184,6 +190,14 @@ public class UserDbTester {
     return db.getDbClient().groupDao().selectByName(db.getSession(), name);
   }
 
+  public int countAllGroups() {
+    return db.getDbClient().groupDao().countByQuery(db.getSession(), new GroupQuery(null, null));
+  }
+
+  public Optional<ExternalGroupDto> selectExternalGroupByGroupUuid(String groupUuid) {
+    return db.getDbClient().externalGroupDao().selectByGroupUuid(db.getSession(), groupUuid);
+  }
+
   // GROUP MEMBERSHIP
 
   public UserGroupDto insertMember(GroupDto group, UserDto user) {
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/ExternalGroupServiceIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/ExternalGroupServiceIT.java
new file mode 100644 (file)
index 0000000..a355dbf
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * 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 org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactoryFast;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.user.ExternalGroupDto;
+import org.sonar.db.user.GroupDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ExternalGroupServiceIT {
+
+  private static final String GROUP_NAME = "GROUP_NAME";
+  private static final String EXTERNAL_ID = "EXTERNAL_ID";
+  private static final String EXTERNAL_IDENTITY_PROVIDER = "EXTERNAL_IDENTITY_PROVIDER";
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  private final DbClient dbClient = dbTester.getDbClient();
+  private final DbSession dbSession = dbTester.getSession();
+  private final GroupService groupService = new GroupService(dbClient, UuidFactoryFast.getInstance());
+
+  private final ExternalGroupService externalGroupService = new ExternalGroupService(dbClient, groupService);
+
+  @Test
+  public void createOrUpdateExternalGroup_whenNewGroup_shouldCreateIt() {
+    externalGroupService.createOrUpdateExternalGroup(dbSession, new GroupRegistration(EXTERNAL_ID, EXTERNAL_IDENTITY_PROVIDER, GROUP_NAME));
+
+    assertGroupAndExternalGroup();
+  }
+
+  @Test
+  public void createOrUpdateExternalGroup_whenExistingLocalGroup_shouldMatchAndMakeItExternal() {
+    dbTester.users().insertGroup(GROUP_NAME);
+
+    externalGroupService.createOrUpdateExternalGroup(dbSession, new GroupRegistration(EXTERNAL_ID, EXTERNAL_IDENTITY_PROVIDER, GROUP_NAME));
+
+    assertThat(dbTester.users().countAllGroups()).isEqualTo(1);
+    assertGroupAndExternalGroup();
+  }
+
+  @Test
+  public void createOrUpdateExternalGroup_whenExistingExternalGroup_shouldUpdate() {
+    dbTester.users().insertDefaultGroup();
+    GroupDto existingGroupDto = dbTester.users().insertGroup(GROUP_NAME);
+    dbTester.users().insertExternalGroup(new ExternalGroupDto(existingGroupDto.getUuid(), EXTERNAL_ID, EXTERNAL_IDENTITY_PROVIDER));
+
+    String updatedGroupName = "updated_" + GROUP_NAME;
+    externalGroupService.createOrUpdateExternalGroup(dbSession, new GroupRegistration(EXTERNAL_ID, EXTERNAL_IDENTITY_PROVIDER, updatedGroupName));
+
+    Optional<GroupDto> groupDto = dbTester.users().selectGroup(updatedGroupName);
+    assertThat(groupDto)
+      .isPresent().get()
+      .extracting(GroupDto::getName)
+      .isEqualTo(updatedGroupName);
+  }
+
+  private void assertGroupAndExternalGroup() {
+    Optional<GroupDto> groupDto = dbTester.users().selectGroup(GROUP_NAME);
+    assertThat(groupDto)
+      .isPresent().get()
+      .extracting(GroupDto::getName).isEqualTo(GROUP_NAME);
+
+    assertThat((dbTester.users().selectExternalGroupByGroupUuid(groupDto.get().getUuid())))
+      .isPresent().get()
+      .extracting(ExternalGroupDto::externalId, ExternalGroupDto::externalIdentityProvider)
+      .containsExactly(EXTERNAL_ID, EXTERNAL_IDENTITY_PROVIDER);
+  }
+
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/ExternalGroupService.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/ExternalGroupService.java
new file mode 100644 (file)
index 0000000..65ead0b
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * 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 org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.user.ExternalGroupDto;
+import org.sonar.db.user.GroupDto;
+
+public class ExternalGroupService {
+
+  private final DbClient dbClient;
+  private final GroupService groupService;
+
+  public ExternalGroupService(DbClient dbClient, GroupService groupService) {
+    this.dbClient = dbClient;
+    this.groupService = groupService;
+  }
+
+  public void createOrUpdateExternalGroup(DbSession dbSession, GroupRegistration groupRegistration) {
+    Optional<GroupDto> groupDto = retrieveGroupByItsExternalInformation(dbSession, groupRegistration);
+    if (groupDto.isPresent()) {
+      updateExternalGroup(dbSession, groupDto.get(), groupRegistration.name());
+    } else {
+      createOrMatchExistingLocalGroup(dbSession, groupRegistration);
+    }
+  }
+
+  private Optional<GroupDto> retrieveGroupByItsExternalInformation(DbSession dbSession, GroupRegistration groupRegistration) {
+    Optional<ExternalGroupDto> externalGroupDto =
+      dbClient.externalGroupDao().selectByExternalIdAndIdentityProvider(dbSession, groupRegistration.externalId(), groupRegistration.externalIdentityProvider());
+    return externalGroupDto.flatMap(existingExternalGroupDto -> Optional.ofNullable(dbClient.groupDao().selectByUuid(dbSession, existingExternalGroupDto.groupUuid())));
+  }
+
+  private void updateExternalGroup(DbSession dbSession, GroupDto groupDto, String newName) {
+    groupService.updateGroup(dbSession, groupDto, newName);
+  }
+
+  private void createOrMatchExistingLocalGroup(DbSession dbSession, GroupRegistration groupRegistration) {
+    GroupDto groupDto = findOrCreateLocalGroup(dbSession, groupRegistration);
+    createExternalGroup(dbSession, groupDto.getUuid(), groupRegistration);
+  }
+
+  private GroupDto findOrCreateLocalGroup(DbSession dbSession, GroupRegistration groupRegistration) {
+    Optional<GroupDto> groupDto = groupService.findGroup(dbSession, groupRegistration.name());
+    return groupDto.orElseGet(() -> groupService.createGroup(dbSession, groupRegistration.name(), null));
+  }
+
+  private void createExternalGroup(DbSession dbSession, String groupUuid, GroupRegistration groupRegistration) {
+    dbClient.externalGroupDao().insert(dbSession, new ExternalGroupDto(groupUuid, groupRegistration.externalId(), groupRegistration.externalIdentityProvider()));
+  }
+
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/GroupRegistration.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/GroupRegistration.java
new file mode 100644 (file)
index 0000000..cafa26e
--- /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.
+ */
+package org.sonar.server.usergroups.ws;
+
+public record GroupRegistration(String externalId, String externalIdentityProvider, String name) {
+}