--- /dev/null
+/*
+ * 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));
+ }
+ }
+}
.setEmail(email);
if (teams != null) {
builder.setGroups(teams.stream()
- .map(team -> team.getOrganizationId() + "/" + team.getId())
+ .map(GithubTeamConverter::toGroupName)
.collect(Collectors.toSet()));
}
return builder.build();
--- /dev/null
+/*
+ * 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();
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+}
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;
EsQueueDao.class,
EventDao.class,
EventComponentChangeDao.class,
+ ExternalGroupDao.class,
FileSourceDao.class,
GroupDao.class,
GroupMembershipDao.class,
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;
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;
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);
return groupDao;
}
+ public ExternalGroupDao externalGroupDao() {
+ return externalGroupDao;
+ }
+
public RuleDao ruleDao() {
return ruleDao;
}
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;
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);
EsQueueMapper.class,
EventMapper.class,
EventComponentChangeMapper.class,
+ ExternalGroupMapper.class,
FileSourceMapper.class,
GroupMapper.class,
GroupMembershipMapper.class,
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+
+}
--- /dev/null
+<?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>
* 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);