]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19085 Add step to fetch groups from DB
authorWojtek Wajerowicz <115081248+wojciech-wajerowicz-sonarsource@users.noreply.github.com>
Thu, 4 May 2023 07:16:57 +0000 (09:16 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 11 May 2023 20:03:14 +0000 (20:03 +0000)
12 files changed:
server/sonar-auth-github/src/main/java/org/sonar/auth/github/GithubTeamConverter.java [new file with mode: 0644]
server/sonar-auth-github/src/main/java/org/sonar/auth/github/UserIdentityFactoryImpl.java
server/sonar-auth-github/src/test/java/org/sonar/auth/github/GithubTeamConverterTest.java [new file with mode: 0644]
server/sonar-db-dao/src/it/java/org/sonar/db/user/ExternalGroupDaoIT.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
server/sonar-db-dao/src/main/java/org/sonar/db/user/ExternalGroupDao.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/user/ExternalGroupDto.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/user/ExternalGroupMapper.java [new file with mode: 0644]
server/sonar-db-dao/src/main/resources/org/sonar/db/user/ExternalGroupMapper.xml [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/es/searchrequest/TopAggregationHelper.java

diff --git a/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GithubTeamConverter.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GithubTeamConverter.java
new file mode 100644 (file)
index 0000000..be7bc9c
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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.auth.github;
+
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class GithubTeamConverter {
+
+  private static final Pattern TEAM_NAME_PATTERN = Pattern.compile("([^/]+)/(.+)");
+
+  private GithubTeamConverter() {
+  }
+
+  public static String toGroupName(GsonTeam team) {
+    return team.getOrganizationId() + "/" + team.getId();
+  }
+
+  public static Optional<String> extractOrganizationName(String groupName) {
+    return extractRegexGroupIfMatches(groupName, 1);
+  }
+
+  public static Optional<String> extractTeamName(String groupName) {
+    return extractRegexGroupIfMatches(groupName, 2);
+  }
+
+  private static Optional<String> extractRegexGroupIfMatches(String groupName, int regexGroup) {
+    Matcher matcher = TEAM_NAME_PATTERN.matcher(groupName);
+    if (!matcher.matches()) {
+      return Optional.empty();
+    } else {
+      return Optional.of(matcher.group(regexGroup));
+    }
+  }
+}
index 276ea9cdc7e517d7772bc9988ce606ee83e500ae..7caa3a602df370cb0a75a350b837b91852d8052c 100644 (file)
@@ -35,7 +35,7 @@ public class UserIdentityFactoryImpl implements UserIdentityFactory {
       .setEmail(email);
     if (teams != null) {
       builder.setGroups(teams.stream()
-        .map(team -> team.getOrganizationId() + "/" + team.getId())
+        .map(GithubTeamConverter::toGroupName)
         .collect(Collectors.toSet()));
     }
     return builder.build();
diff --git a/server/sonar-auth-github/src/test/java/org/sonar/auth/github/GithubTeamConverterTest.java b/server/sonar-auth-github/src/test/java/org/sonar/auth/github/GithubTeamConverterTest.java
new file mode 100644 (file)
index 0000000..2f7ad32
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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.auth.github;
+
+import java.util.Optional;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class GithubTeamConverterTest {
+
+  @Test
+  public void toGroup_creates_correct_groupName() {
+    GsonTeam team = new GsonTeam("team-1", new GsonTeam.GsonOrganization("Org1"));
+    assertThat(GithubTeamConverter.toGroupName(team)).isEqualTo("Org1/team-1");
+  }
+
+  @Test
+  public void extractOrganizationName_whenNameIsCorrect_extractsOrganizationName() {
+    assertThat(GithubTeamConverter.extractOrganizationName("Org1/team1")).isEqualTo(Optional.of("Org1"));
+    assertThat(GithubTeamConverter.extractOrganizationName("Org1/team1/team2")).isEqualTo(Optional.of("Org1"));
+  }
+
+  @Test
+  public void extractOrganizationName_whenNameIsIncorrect_returnEmpty() {
+    assertThat(GithubTeamConverter.extractOrganizationName("Org1")).isEmpty();
+    assertThat(GithubTeamConverter.extractOrganizationName("Org1/")).isEmpty();
+  }
+
+  @Test
+  public void extractTeamName_whenNameIsCorrect_extractsTeamName() {
+    assertThat(GithubTeamConverter.extractTeamName("Org1/team1")).isEqualTo(Optional.of("team1"));
+    assertThat(GithubTeamConverter.extractTeamName("Org1/team1/team2")).isEqualTo(Optional.of("team1/team2"));
+  }
+
+  @Test
+  public void extractTeamName_whenNameIsIncorrect_returnEmpty() {
+    assertThat(GithubTeamConverter.extractTeamName("Org1")).isEmpty();
+    assertThat(GithubTeamConverter.extractTeamName("Org1/")).isEmpty();
+  }
+
+}
diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/user/ExternalGroupDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/user/ExternalGroupDaoIT.java
new file mode 100644 (file)
index 0000000..8b79088
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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.db.user;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ExternalGroupDaoIT {
+
+  @Rule
+  public final DbTester db = DbTester.create();
+
+  private final DbSession dbSession = db.getSession();
+
+  private final GroupDao groupDao = db.getDbClient().groupDao();
+
+  private final ExternalGroupDao underTest = db.getDbClient().externalGroupDao();
+
+  @Test
+  public void insert_savesExternalGroup() {
+    GroupDto localGroup = insertGroup("12345");
+    insertGroup("67689");
+    ExternalGroupDto externalGroupDto = externalGroup("12345", "providerId");
+    underTest.insert(dbSession, externalGroupDto);
+    List<ExternalGroupDto> savedGroups = underTest.selectByIdentityProvider(dbSession, "providerId");
+    assertThat(savedGroups)
+      .hasSize(1)
+      .contains(createExternalGroupDto(localGroup.getName(), externalGroupDto));
+  }
+
+  @Test
+  public void selectByIdentityProvider_returnOnlyGroupForTheIdentityProvider() {
+    List<ExternalGroupDto> expectedGroups = createAndInsertExternalGroupDtos("provider1", 3);
+    createAndInsertExternalGroupDtos("provider2", 1);
+    List<ExternalGroupDto> savedGroup = underTest.selectByIdentityProvider(dbSession, "provider1");
+    assertThat(savedGroup).containsExactlyInAnyOrderElementsOf(expectedGroups);
+  }
+
+  private List<ExternalGroupDto> createAndInsertExternalGroupDtos(String provider, int numberOfGroups) {
+    List<ExternalGroupDto> expectedExternalGroupDtos = new ArrayList<>();
+    for (int i = 1; i <= numberOfGroups; i++) {
+      ExternalGroupDto externalGroupDto = externalGroup(provider + "_" + i, provider);
+      GroupDto localGroup = insertGroup(externalGroupDto.groupUuid());
+      underTest.insert(dbSession, externalGroupDto);
+      expectedExternalGroupDtos.add(createExternalGroupDto(localGroup.getName(), externalGroupDto));
+    }
+    return expectedExternalGroupDtos;
+  }
+
+  private static ExternalGroupDto externalGroup(String groupUuid, String identityProvider) {
+    return new ExternalGroupDto(groupUuid, "external_" + groupUuid, identityProvider);
+  }
+
+  private GroupDto insertGroup(String groupUuid) {
+    GroupDto group = new GroupDto();
+    group.setUuid(groupUuid);
+    group.setName("name" + groupUuid);
+    return groupDao.insert(dbSession, group);
+  }
+
+  private ExternalGroupDto createExternalGroupDto(String name, ExternalGroupDto externalGroupDto) {
+    return new ExternalGroupDto(externalGroupDto.groupUuid(), externalGroupDto.externalId(), externalGroupDto.externalIdentityProvider(), name);
+  }
+
+}
index c3598e3b8328afed8e65eb36a0b69d5ae08e1f71..a58e3b8d522d5eb907957b15fd740832715c0e86 100644 (file)
@@ -84,6 +84,7 @@ import org.sonar.db.schemamigration.SchemaMigrationDao;
 import org.sonar.db.scim.ScimGroupDao;
 import org.sonar.db.scim.ScimUserDao;
 import org.sonar.db.source.FileSourceDao;
+import org.sonar.db.user.ExternalGroupDao;
 import org.sonar.db.user.GroupDao;
 import org.sonar.db.user.GroupMembershipDao;
 import org.sonar.db.user.RoleDao;
@@ -120,6 +121,7 @@ public class DaoModule extends Module {
     EsQueueDao.class,
     EventDao.class,
     EventComponentChangeDao.class,
+    ExternalGroupDao.class,
     FileSourceDao.class,
     GroupDao.class,
     GroupMembershipDao.class,
index 906edafaee23f3cb761cc81dbcc4e7e9fae1d81e..4179b7dc7e1fcdbaf91d4f67d75263f725beea26 100644 (file)
@@ -84,6 +84,7 @@ import org.sonar.db.schemamigration.SchemaMigrationDao;
 import org.sonar.db.scim.ScimGroupDao;
 import org.sonar.db.scim.ScimUserDao;
 import org.sonar.db.source.FileSourceDao;
+import org.sonar.db.user.ExternalGroupDao;
 import org.sonar.db.user.GroupDao;
 import org.sonar.db.user.GroupMembershipDao;
 import org.sonar.db.user.RoleDao;
@@ -149,6 +150,7 @@ public class DbClient {
   private final NotificationQueueDao notificationQueueDao;
   private final MetricDao metricDao;
   private final GroupDao groupDao;
+  private final ExternalGroupDao externalGroupDao;
   private final RuleDao ruleDao;
   private final RuleRepositoryDao ruleRepositoryDao;
   private final ActiveRuleDao activeRuleDao;
@@ -232,6 +234,7 @@ public class DbClient {
     notificationQueueDao = getDao(map, NotificationQueueDao.class);
     metricDao = getDao(map, MetricDao.class);
     groupDao = getDao(map, GroupDao.class);
+    externalGroupDao = getDao(map, ExternalGroupDao.class);
     ruleDao = getDao(map, RuleDao.class);
     ruleRepositoryDao = getDao(map, RuleRepositoryDao.class);
     activeRuleDao = getDao(map, ActiveRuleDao.class);
@@ -470,6 +473,10 @@ public class DbClient {
     return groupDao;
   }
 
+  public ExternalGroupDao externalGroupDao() {
+    return externalGroupDao;
+  }
+
   public RuleDao ruleDao() {
     return ruleDao;
   }
index 78dca8e791c609bcdb9faf5730532be88aabc800..c87a74221e7218e40c3c8269b7773d615a2b1c9e 100644 (file)
@@ -145,6 +145,8 @@ import org.sonar.db.schemamigration.SchemaMigrationMapper;
 import org.sonar.db.scim.ScimGroupMapper;
 import org.sonar.db.scim.ScimUserMapper;
 import org.sonar.db.source.FileSourceMapper;
+import org.sonar.db.user.ExternalGroupDto;
+import org.sonar.db.user.ExternalGroupMapper;
 import org.sonar.db.user.GroupDto;
 import org.sonar.db.user.GroupMapper;
 import org.sonar.db.user.GroupMembershipDto;
@@ -194,6 +196,7 @@ public class MyBatis {
     confBuilder.loadAlias("Component", ComponentDto.class);
     confBuilder.loadAlias("DuplicationUnit", DuplicationUnitDto.class);
     confBuilder.loadAlias("Event", EventDto.class);
+    confBuilder.loadAlias("ExternalGroup", ExternalGroupDto.class);
     confBuilder.loadAlias("FilePathWithHash", FilePathWithHashDto.class);
     confBuilder.loadAlias("KeyWithUuid", KeyWithUuidDto.class);
     confBuilder.loadAlias("Group", GroupDto.class);
@@ -269,6 +272,7 @@ public class MyBatis {
       EsQueueMapper.class,
       EventMapper.class,
       EventComponentChangeMapper.class,
+      ExternalGroupMapper.class,
       FileSourceMapper.class,
       GroupMapper.class,
       GroupMembershipMapper.class,
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/ExternalGroupDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/ExternalGroupDao.java
new file mode 100644 (file)
index 0000000..e208eba
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * 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.db.user;
+
+import java.util.List;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+
+public class ExternalGroupDao implements Dao {
+
+  public void insert(DbSession dbSession, ExternalGroupDto externalGroupDto) {
+    mapper(dbSession).insert(externalGroupDto);
+  }
+
+  public List<ExternalGroupDto> selectByIdentityProvider(DbSession dbSession, String identityProvider) {
+    return mapper(dbSession).selectByIdentityProvider(identityProvider);
+  }
+
+  private static ExternalGroupMapper mapper(DbSession session) {
+    return session.getMapper(ExternalGroupMapper.class);
+  }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/ExternalGroupDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/ExternalGroupDto.java
new file mode 100644 (file)
index 0000000..03d2908
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * 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.db.user;
+
+import javax.annotation.Nullable;
+
+public record ExternalGroupDto(String groupUuid, String externalId, String externalIdentityProvider, @Nullable String name) {
+
+  public ExternalGroupDto(String groupUuid, String externalId, String externalIdentityProvider) {
+    this(groupUuid, externalId, externalIdentityProvider, null);
+  }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/ExternalGroupMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/ExternalGroupMapper.java
new file mode 100644 (file)
index 0000000..58f632c
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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.db.user;
+
+import java.util.List;
+
+public interface ExternalGroupMapper {
+
+  void insert(ExternalGroupDto externalGroupDto);
+
+  List<ExternalGroupDto> selectByIdentityProvider(String identityProvider);
+
+}
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/user/ExternalGroupMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/user/ExternalGroupMapper.xml
new file mode 100644 (file)
index 0000000..561a240
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.user.ExternalGroupMapper">
+
+  <insert id="insert" useGeneratedKeys="false" parameterType="ExternalGroup">
+    insert into external_groups (
+      group_uuid,
+      external_group_id,
+      external_identity_provider
+    ) values (
+      #{groupUuid,jdbcType=VARCHAR},
+      #{externalId,jdbcType=VARCHAR},
+      #{externalIdentityProvider,jdbcType=VARCHAR}
+    )
+  </insert>
+
+  <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,
+      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>
+
+</mapper>
index 024455b5bee352a3d11f0f1dc664d5012381b035..a44e31896ef91f5cda992a9f3bfe853657e1ac3e 100644 (file)
@@ -82,9 +82,13 @@ public class TopAggregationHelper {
    * top-term sub aggregation based field defined by {@link TopAggregationDefinition.FilterScope#getFieldName()} of
    * {@link TopAggregationDefinition#getFilterScope()}.
    */
-  public FilterAggregationBuilder buildTermTopAggregation(String topAggregationName,
-    TopAggregationDefinition<?> topAggregation, @Nullable Integer numberOfTerms,
-    Consumer<BoolQueryBuilder> extraFilters, Consumer<FilterAggregationBuilder> otherSubAggregations) {
+  public FilterAggregationBuilder buildTermTopAggregation(
+    String topAggregationName,
+    TopAggregationDefinition<?> topAggregation,
+    @Nullable Integer numberOfTerms,
+    Consumer<BoolQueryBuilder> extraFilters,
+    Consumer<FilterAggregationBuilder> otherSubAggregations
+  ) {
     Consumer<FilterAggregationBuilder> subAggregations = t -> {
       t.subAggregation(subAggregationHelper.buildTermsAggregation(topAggregationName, topAggregation, numberOfTerms));
       otherSubAggregations.accept(t);