From ca468111e1e8425cdbc9b9c23e80c3ab563f6115 Mon Sep 17 00:00:00 2001 From: Wojtek Wajerowicz <115081248+wojciech-wajerowicz-sonarsource@users.noreply.github.com> Date: Thu, 4 May 2023 09:16:57 +0200 Subject: [PATCH] SONAR-19085 Add step to fetch groups from DB --- .../auth/github/GithubTeamConverter.java | 53 +++++++++++ .../auth/github/UserIdentityFactoryImpl.java | 2 +- .../auth/github/GithubTeamConverterTest.java | 59 +++++++++++++ .../org/sonar/db/user/ExternalGroupDaoIT.java | 88 +++++++++++++++++++ .../src/main/java/org/sonar/db/DaoModule.java | 2 + .../src/main/java/org/sonar/db/DbClient.java | 7 ++ .../src/main/java/org/sonar/db/MyBatis.java | 4 + .../org/sonar/db/user/ExternalGroupDao.java | 39 ++++++++ .../org/sonar/db/user/ExternalGroupDto.java | 29 ++++++ .../sonar/db/user/ExternalGroupMapper.java | 30 +++++++ .../org/sonar/db/user/ExternalGroupMapper.xml | 30 +++++++ .../searchrequest/TopAggregationHelper.java | 10 ++- 12 files changed, 349 insertions(+), 4 deletions(-) create mode 100644 server/sonar-auth-github/src/main/java/org/sonar/auth/github/GithubTeamConverter.java create mode 100644 server/sonar-auth-github/src/test/java/org/sonar/auth/github/GithubTeamConverterTest.java create mode 100644 server/sonar-db-dao/src/it/java/org/sonar/db/user/ExternalGroupDaoIT.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/user/ExternalGroupDao.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/user/ExternalGroupDto.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/user/ExternalGroupMapper.java create mode 100644 server/sonar-db-dao/src/main/resources/org/sonar/db/user/ExternalGroupMapper.xml 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 index 00000000000..be7bc9cd432 --- /dev/null +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GithubTeamConverter.java @@ -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 extractOrganizationName(String groupName) { + return extractRegexGroupIfMatches(groupName, 1); + } + + public static Optional extractTeamName(String groupName) { + return extractRegexGroupIfMatches(groupName, 2); + } + + private static Optional 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)); + } + } +} diff --git a/server/sonar-auth-github/src/main/java/org/sonar/auth/github/UserIdentityFactoryImpl.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/UserIdentityFactoryImpl.java index 276ea9cdc7e..7caa3a602df 100644 --- a/server/sonar-auth-github/src/main/java/org/sonar/auth/github/UserIdentityFactoryImpl.java +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/UserIdentityFactoryImpl.java @@ -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 index 00000000000..2f7ad3288be --- /dev/null +++ b/server/sonar-auth-github/src/test/java/org/sonar/auth/github/GithubTeamConverterTest.java @@ -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 index 00000000000..8b790881b36 --- /dev/null +++ b/server/sonar-db-dao/src/it/java/org/sonar/db/user/ExternalGroupDaoIT.java @@ -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 savedGroups = underTest.selectByIdentityProvider(dbSession, "providerId"); + assertThat(savedGroups) + .hasSize(1) + .contains(createExternalGroupDto(localGroup.getName(), externalGroupDto)); + } + + @Test + public void selectByIdentityProvider_returnOnlyGroupForTheIdentityProvider() { + List expectedGroups = createAndInsertExternalGroupDtos("provider1", 3); + createAndInsertExternalGroupDtos("provider2", 1); + List savedGroup = underTest.selectByIdentityProvider(dbSession, "provider1"); + assertThat(savedGroup).containsExactlyInAnyOrderElementsOf(expectedGroups); + } + + private List createAndInsertExternalGroupDtos(String provider, int numberOfGroups) { + List 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); + } + +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java index c3598e3b832..a58e3b8d522 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java @@ -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, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java index 906edafaee2..4179b7dc7e1 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java @@ -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; } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java index 78dca8e791c..c87a74221e7 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java @@ -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 index 00000000000..e208eba651d --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/ExternalGroupDao.java @@ -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 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 index 00000000000..03d29082fbf --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/ExternalGroupDto.java @@ -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 index 00000000000..58f632c0eba --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/ExternalGroupMapper.java @@ -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 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 index 00000000000..561a24093a6 --- /dev/null +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/user/ExternalGroupMapper.xml @@ -0,0 +1,30 @@ + + + + + + + + insert into external_groups ( + group_uuid, + external_group_id, + external_identity_provider + ) values ( + #{groupUuid,jdbcType=VARCHAR}, + #{externalId,jdbcType=VARCHAR}, + #{externalIdentityProvider,jdbcType=VARCHAR} + ) + + + + + diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/searchrequest/TopAggregationHelper.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/searchrequest/TopAggregationHelper.java index 024455b5bee..a44e31896ef 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/es/searchrequest/TopAggregationHelper.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/es/searchrequest/TopAggregationHelper.java @@ -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 extraFilters, Consumer otherSubAggregations) { + public FilterAggregationBuilder buildTermTopAggregation( + String topAggregationName, + TopAggregationDefinition topAggregation, + @Nullable Integer numberOfTerms, + Consumer extraFilters, + Consumer otherSubAggregations + ) { Consumer subAggregations = t -> { t.subAggregation(subAggregationHelper.buildTermsAggregation(topAggregationName, topAggregation, numberOfTerms)); otherSubAggregations.accept(t); -- 2.39.5