3 * Copyright (C) 2009-2024 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.server.common.almsettings.github;
23 import javax.annotation.CheckForNull;
24 import org.sonar.alm.client.github.GithubPermissionConverter;
25 import org.sonar.api.web.UserRole;
26 import org.sonar.auth.DevOpsPlatformSettings;
27 import org.sonar.auth.github.AppInstallationToken;
28 import org.sonar.auth.github.GsonRepositoryCollaborator;
29 import org.sonar.auth.github.GsonRepositoryPermissions;
30 import org.sonar.auth.github.GsonRepositoryTeam;
31 import org.sonar.auth.github.client.GithubApplicationClient;
32 import org.sonar.db.DbClient;
33 import org.sonar.db.provisioning.GithubPermissionsMappingDto;
34 import org.sonar.db.user.GroupDto;
35 import org.sonar.server.common.almintegration.ProjectKeyGenerator;
36 import org.sonar.server.common.almsettings.DefaultDevOpsProjectCreator;
37 import org.sonar.server.common.almsettings.DevOpsProjectCreationContext;
38 import org.sonar.server.common.permission.PermissionUpdater;
39 import org.sonar.server.common.permission.UserPermissionChange;
40 import org.sonar.server.common.project.ProjectCreator;
41 import org.sonar.server.management.ManagedProjectService;
42 import org.sonar.server.permission.PermissionService;
43 import org.sonar.server.user.UserSession;
45 import static java.util.Objects.requireNonNull;
46 import static java.util.stream.Collectors.toSet;
47 import static org.sonar.api.utils.Preconditions.checkState;
49 public class GithubProjectCreator extends DefaultDevOpsProjectCreator {
51 private final GithubApplicationClient githubApplicationClient;
52 private final GithubPermissionConverter githubPermissionConverter;
55 private final AppInstallationToken authAppInstallationToken;
57 public GithubProjectCreator(DbClient dbClient, DevOpsProjectCreationContext devOpsProjectCreationContext,
58 ProjectKeyGenerator projectKeyGenerator,
59 DevOpsPlatformSettings devOpsPlatformSettings, ProjectCreator projectCreator, PermissionService permissionService, PermissionUpdater<UserPermissionChange> permissionUpdater,
60 ManagedProjectService managedProjectService, GithubApplicationClient githubApplicationClient, GithubPermissionConverter githubPermissionConverter,
61 @CheckForNull AppInstallationToken authAppInstallationToken) {
62 super(dbClient, devOpsProjectCreationContext, projectKeyGenerator, devOpsPlatformSettings, projectCreator, permissionService, permissionUpdater,
63 managedProjectService);
64 this.githubApplicationClient = githubApplicationClient;
65 this.githubPermissionConverter = githubPermissionConverter;
66 this.authAppInstallationToken = authAppInstallationToken;
70 public boolean isScanAllowedUsingPermissionsFromDevopsPlatform() {
71 checkState(authAppInstallationToken != null, "An auth app token is required in case repository permissions checking is necessary.");
73 String[] orgaAndRepoTokenified = devOpsProjectCreationContext.fullName().split("/");
74 String organization = orgaAndRepoTokenified[0];
75 String repository = orgaAndRepoTokenified[1];
77 Set<GithubPermissionsMappingDto> permissionsMappingDtos = dbClient.githubPermissionsMappingDao().findAll(dbClient.openSession(false));
79 boolean userHasDirectAccessToRepo = doesUserHaveScanPermission(organization, repository, permissionsMappingDtos);
80 if (userHasDirectAccessToRepo) {
83 return doesUserBelongToAGroupWithScanPermission(organization, repository, permissionsMappingDtos);
86 private boolean doesUserHaveScanPermission(String organization, String repository, Set<GithubPermissionsMappingDto> permissionsMappingDtos) {
87 String url = requireNonNull(devOpsProjectCreationContext.almSettingDto().getUrl(), "GitHub url not defined");
88 Set<GsonRepositoryCollaborator> repositoryCollaborators = githubApplicationClient.getRepositoryCollaborators(url, authAppInstallationToken, organization, repository);
90 UserSession userSession = devOpsProjectCreationContext.userSession();
91 String externalLogin = userSession.getExternalIdentity().map(UserSession.ExternalIdentity::login).orElse(null);
92 if (externalLogin == null) {
95 return repositoryCollaborators.stream()
96 .filter(gsonRepositoryCollaborator -> externalLogin.equals(gsonRepositoryCollaborator.name()))
98 .map(gsonRepositoryCollaborator -> hasScanPermission(permissionsMappingDtos, gsonRepositoryCollaborator.roleName(), gsonRepositoryCollaborator.permissions()))
102 private boolean doesUserBelongToAGroupWithScanPermission(String organization, String repository,
103 Set<GithubPermissionsMappingDto> permissionsMappingDtos) {
104 String url = requireNonNull(devOpsProjectCreationContext.almSettingDto().getUrl(), "GitHub url not defined");
105 Set<GsonRepositoryTeam> repositoryTeams = githubApplicationClient.getRepositoryTeams(url, authAppInstallationToken, organization, repository);
107 Set<String> groupsOfUser = findUserMembershipOnSonarQube(organization);
108 return repositoryTeams.stream()
109 .filter(team -> hasScanPermission(permissionsMappingDtos, team.permission(), team.permissions()))
110 .map(GsonRepositoryTeam::name)
111 .anyMatch(groupsOfUser::contains);
114 private Set<String> findUserMembershipOnSonarQube(String organization) {
115 return devOpsProjectCreationContext.userSession().getGroups().stream()
116 .map(GroupDto::getName)
117 .filter(groupName -> groupName.contains("/"))
118 .map(name -> name.replaceFirst(organization + "/", ""))
122 private boolean hasScanPermission(Set<GithubPermissionsMappingDto> permissionsMappingDtos, String role, GsonRepositoryPermissions permissions) {
123 Set<String> sonarqubePermissions = githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(permissionsMappingDtos,
125 return sonarqubePermissions.contains(UserRole.SCAN);