]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20700 Check project's permissions on GitHub in case auto-provisioning is on
authorAurelien Poscia <aurelien.poscia@sonarsource.com>
Tue, 17 Oct 2023 06:55:05 +0000 (08:55 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 20 Oct 2023 20:02:40 +0000 (20:02 +0000)
server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/ce/queue/BranchReportSubmitterIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/ce/queue/ReportSubmitterIT.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DelegatingDevOpsPlatformService.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsPlatformService.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GitHubDevOpsPlatformService.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/queue/ReportSubmitter.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/DelegatingDevOpsPlatformServiceTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GitHubDevOpsPlatformServiceTest.java
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java

index 501c5bbc09a5e1bdce98610fc2211de2ae8e0492..9ebc0dce37cbf50e5817648d21771929740257c4 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.System2;
 import org.sonar.auth.github.GitHubSettings;
+import org.sonar.auth.github.GithubPermissionConverter;
 import org.sonar.core.i18n.I18n;
 import org.sonar.core.platform.EditionProvider;
 import org.sonar.core.platform.PlatformEditionProvider;
@@ -129,8 +130,10 @@ public class ImportGithubProjectActionIT {
   private final NewCodeDefinitionResolver newCodeDefinitionResolver = new NewCodeDefinitionResolver(db.getDbClient(), editionProvider);
 
   private final ManagedProjectService managedProjectService = mock(ManagedProjectService.class);
+
+  private final GithubPermissionConverter githubPermissionConverter = new GithubPermissionConverter();
   private final GitHubDevOpsPlatformService gitHubDevOpsPlatformService = new GitHubDevOpsPlatformService(db.getDbClient(),
-    null, appClient, projectDefaultVisibility, projectKeyGenerator, userSession, componentUpdater, gitHubSettings);
+    null, appClient, projectDefaultVisibility, projectKeyGenerator, userSession, componentUpdater, gitHubSettings, githubPermissionConverter);
   private final WsActionTester ws = new WsActionTester(new ImportGithubProjectAction(db.getDbClient(), managedProjectService, userSession,
     componentUpdater, importHelper, newCodeDefinitionResolver, defaultBranchNameResolver, gitHubDevOpsPlatformService));
 
index 06da5572a64deb88cd542274718d05ba66d778de..4bcbd73524cda1e41a5b45de400e7fdac7faa52c 100644 (file)
@@ -108,7 +108,7 @@ public class BranchReportSubmitterIT {
   private final BranchSupport branchSupport = spy(new BranchSupport(branchSupportDelegate));
 
   private final DevOpsPlatformService devOpsPlatformService = new GitHubDevOpsPlatformService(db.getDbClient(), null,
-    null, projectDefaultVisibility, null, userSession, componentUpdater, null);
+    null, projectDefaultVisibility, null, userSession, componentUpdater, null, null);
 
   private final ManagedInstanceService managedInstanceService = mock();
   private final ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentUpdater, permissionTemplateService, db.getDbClient(), branchSupport,
index 3a30c77763d286897bdbe477d880a13c0b87ad4c..fd1cf3fb2e03edfb8ae4862b8a04693dc3f3ec62 100644 (file)
@@ -76,6 +76,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Collections.emptyMap;
 import static java.util.stream.IntStream.rangeClosed;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
@@ -123,7 +124,7 @@ public class ReportSubmitterIT {
 
   private final DevOpsPlatformService devOpsPlatformService = new DelegatingDevOpsPlatformService(
     Set.of(new GitHubDevOpsPlatformService(db.getDbClient(), githubGlobalSettingsValidator,
-      githubApplicationClient, projectDefaultVisibility, projectKeyGenerator, userSession, componentUpdater, gitHubSettings)));
+      githubApplicationClient, projectDefaultVisibility, projectKeyGenerator, userSession, componentUpdater, gitHubSettings, null)));
 
   private final DevOpsPlatformService devOpsPlatformServiceSpy = spy(devOpsPlatformService);
 
@@ -275,7 +276,7 @@ public class ReportSubmitterIT {
   }
 
   @Test
-  public void submit_whenReportIsForANewProjectWithoutValidAlmSettings_createsProjectWithoutDevOpsBinding() {
+  public void submit_whenReportIsForANewGithubProjectWithoutValidAlmSettings_throws() {
     userSession.addPermission(GlobalPermission.SCAN).addPermission(PROVISION_PROJECTS);
     when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), any(), eq(PROJECT_KEY))).thenReturn(true);
     mockSuccessfulPrepareSubmitCall();
@@ -284,13 +285,18 @@ public class ReportSubmitterIT {
     DevOpsProjectDescriptor projectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, "apiUrl", "orga/repo");
     when(devOpsPlatformServiceSpy.getDevOpsProjectDescriptor(characteristics)).thenReturn(Optional.of(projectDescriptor));
 
-    underTest.submit(PROJECT_KEY, PROJECT_NAME, characteristics, IOUtils.toInputStream("{binary}", UTF_8));
+    assertThatIllegalArgumentException().isThrownBy(() -> underTest.submit(PROJECT_KEY, PROJECT_NAME, characteristics, IOUtils.toInputStream("{binary}", UTF_8)))
+      .withMessage("The project orga/repo could not be created. It was auto-detected as a GITHUB project and no valid DevOps platform configuration were found to access apiUrl");
 
-    assertLocalProjectWasCreated();
+    assertNoProjectWasCreated();
+  }
+
+  private void assertNoProjectWasCreated() {
+    assertThat(db.getDbClient().projectDao().selectAll(db.getSession())).isEmpty();
   }
 
   @Test
-  public void submit_whenReportIsForANewProjectWithValidAlmSettingsButAutoProvisioningOn_createsLocalProject() {
+  public void submit_whenReportIsForANewProjectWithValidAlmSettingsAutoProvisioningOnAndPermOnGh_createsProjectWithBinding() {
     userSession.addPermission(GlobalPermission.SCAN).addPermission(PROVISION_PROJECTS);
     when(managedInstanceService.isInstanceExternallyManaged()).thenReturn(true);
     when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), any(), eq(PROJECT_KEY))).thenReturn(true);
@@ -299,10 +305,42 @@ public class ReportSubmitterIT {
     Map<String, String> characteristics = Map.of("random", "data");
     DevOpsProjectDescriptor projectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, "apiUrl", "orga/repo");
 
-    mockInteractionsWithDevOpsPlatformServiceSpyBeforeProjectCreation(characteristics, projectDescriptor);
+    AlmSettingDto almSettingDto = mockInteractionsWithDevOpsPlatformServiceSpyBeforeProjectCreation(characteristics, projectDescriptor);
+    when(devOpsPlatformServiceSpy.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, projectDescriptor)).thenReturn(true);
 
     underTest.submit(PROJECT_KEY, PROJECT_NAME, characteristics, IOUtils.toInputStream("{binary}", UTF_8));
 
+    assertProjectWasCreatedWithBinding();
+  }
+
+  @Test
+  public void submit_whenReportIsForANewProjectWithProjectDescriptorAndNoValidAlmSettingsAndAutoProvisioningOn_throws() {
+    userSession.addPermission(GlobalPermission.SCAN).addPermission(PROVISION_PROJECTS);
+    when(managedInstanceService.isInstanceExternallyManaged()).thenReturn(true);
+    when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), any(), eq(PROJECT_KEY))).thenReturn(true);
+    mockSuccessfulPrepareSubmitCall();
+
+    Map<String, String> characteristics = Map.of("random", "data");
+    DevOpsProjectDescriptor projectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, "apiUrl", "orga/repo");
+    doReturn(Optional.of(projectDescriptor)).when(devOpsPlatformServiceSpy).getDevOpsProjectDescriptor(characteristics);
+
+    assertThatIllegalArgumentException()
+      .isThrownBy(() -> underTest.submit(PROJECT_KEY, PROJECT_NAME, characteristics, IOUtils.toInputStream("{binary}", UTF_8)))
+      .withMessage("The project orga/repo could not be created. It was auto-detected as a GITHUB project and no valid DevOps platform configuration were found to access apiUrl");
+
+    assertNoProjectWasCreated();
+  }
+
+  @Test
+  public void submit_whenReportIsForANewProjectWithoutDevOpsMetadataAndAutoProvisioningOn_shouldCreateLocalProject() {
+    userSession.addPermission(GlobalPermission.SCAN).addPermission(PROVISION_PROJECTS);
+    when(managedInstanceService.isInstanceExternallyManaged()).thenReturn(true);
+    when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), any(), eq(PROJECT_KEY))).thenReturn(true);
+    mockSuccessfulPrepareSubmitCall();
+
+    Map<String, String> characteristics = Map.of("random", "data");
+
+    underTest.submit(PROJECT_KEY, PROJECT_NAME, characteristics, IOUtils.toInputStream("{binary}", UTF_8));
     assertLocalProjectWasCreated();
   }
 
@@ -344,7 +382,7 @@ public class ReportSubmitterIT {
     assertThat(db.getDbClient().projectAlmSettingDao().selectByProject(db.getSession(), projectDto.getUuid())).isPresent();
   }
 
-  private void mockInteractionsWithDevOpsPlatformServiceSpyBeforeProjectCreation(Map<String, String> characteristics, DevOpsProjectDescriptor projectDescriptor) {
+  private AlmSettingDto mockInteractionsWithDevOpsPlatformServiceSpyBeforeProjectCreation(Map<String, String> characteristics, DevOpsProjectDescriptor projectDescriptor) {
     doReturn(Optional.of(projectDescriptor)).when(devOpsPlatformServiceSpy).getDevOpsProjectDescriptor(characteristics);
     AlmSettingDto almSettingDto = mock(AlmSettingDto.class);
     when(almSettingDto.getAlm()).thenReturn(ALM.GITHUB);
@@ -352,6 +390,7 @@ public class ReportSubmitterIT {
     when(almSettingDto.getUuid()).thenReturn("TEST_GH");
     doReturn(Optional.of(almSettingDto)).when(devOpsPlatformServiceSpy).getValidAlmSettingDto(any(), eq(projectDescriptor));
     mockGithubInteractions(almSettingDto);
+    return almSettingDto;
   }
 
   private void mockGithubInteractions(AlmSettingDto almSettingDto) {
index dd764473d9f3fe721cb29d36e6aa38096caba83d..608d8cf77d5577165ae4745138089881bfbaf33b 100644 (file)
@@ -59,6 +59,13 @@ public class DelegatingDevOpsPlatformService implements DevOpsPlatformService {
       .flatMap(delegate -> delegate.getValidAlmSettingDto(dbSession, devOpsProjectDescriptor));
   }
 
+  @Override
+  public boolean isScanAllowedUsingPermissionsFromDevopsPlatform(AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor) {
+    return findDelegate(devOpsProjectDescriptor.alm())
+      .map(delegate -> delegate.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, devOpsProjectDescriptor))
+      .orElseThrow(() -> new IllegalStateException("No delegate found to handle projects on " + devOpsProjectDescriptor.alm()));
+  }
+
   @Override
   public ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, String projectKey, AlmSettingDto almSettingDto,
     DevOpsProjectDescriptor devOpsProjectDescriptor) {
index fff70b8add453ff354810fd99646bb3df06f6012..6db06d88fd5593deff12289d10477db296a92622 100644 (file)
@@ -35,6 +35,8 @@ public interface DevOpsPlatformService {
 
   Optional<AlmSettingDto> getValidAlmSettingDto(DbSession dbSession, DevOpsProjectDescriptor devOpsProjectDescriptor);
 
+  boolean isScanAllowedUsingPermissionsFromDevopsPlatform(AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor);
+
   ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, String projectKey, AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor);
 
   ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, AlmSettingDto almSettingDto, AccessToken accessToken,
index 8ac5d5eb8b0f7ae444585d24a781c9cdd450421e..9e69daefbd008fa0c162739d8a1fd624604a3c7a 100644 (file)
@@ -21,16 +21,23 @@ package org.sonar.server.almsettings.ws;
 
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.alm.client.github.AppInstallationToken;
 import org.sonar.alm.client.github.GithubApplicationClient;
 import org.sonar.alm.client.github.GithubGlobalSettingsValidator;
+import org.sonar.alm.client.github.api.GsonRepositoryCollaborator;
+import org.sonar.alm.client.github.api.GsonRepositoryTeam;
 import org.sonar.alm.client.github.config.GithubAppConfiguration;
 import org.sonar.alm.client.github.security.AccessToken;
 import org.sonar.api.server.ServerSide;
+import org.sonar.api.web.UserRole;
 import org.sonar.auth.github.GitHubSettings;
+import org.sonar.auth.github.GithubPermissionConverter;
+import org.sonar.auth.github.GsonRepositoryPermissions;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.alm.setting.ALM;
@@ -38,6 +45,8 @@ import org.sonar.db.alm.setting.AlmSettingDto;
 import org.sonar.db.alm.setting.ProjectAlmSettingDto;
 import org.sonar.db.project.CreationMethod;
 import org.sonar.db.project.ProjectDto;
+import org.sonar.db.provisioning.GithubPermissionsMappingDto;
+import org.sonar.db.user.GroupDto;
 import org.sonar.server.almintegration.ws.ProjectKeyGenerator;
 import org.sonar.server.component.ComponentCreationData;
 import org.sonar.server.component.ComponentCreationParameters;
@@ -70,10 +79,11 @@ public class GitHubDevOpsPlatformService implements DevOpsPlatformService {
   private final UserSession userSession;
   private final ComponentUpdater componentUpdater;
   private final GitHubSettings gitHubSettings;
+  private final GithubPermissionConverter githubPermissionConverter;
 
   public GitHubDevOpsPlatformService(DbClient dbClient, GithubGlobalSettingsValidator githubGlobalSettingsValidator,
     GithubApplicationClient githubApplicationClient, ProjectDefaultVisibility projectDefaultVisibility, ProjectKeyGenerator projectKeyGenerator, UserSession userSession,
-    ComponentUpdater componentUpdater, GitHubSettings gitHubSettings) {
+    ComponentUpdater componentUpdater, GitHubSettings gitHubSettings, GithubPermissionConverter githubPermissionConverter) {
     this.dbClient = dbClient;
     this.githubGlobalSettingsValidator = githubGlobalSettingsValidator;
     this.githubApplicationClient = githubApplicationClient;
@@ -82,6 +92,7 @@ public class GitHubDevOpsPlatformService implements DevOpsPlatformService {
     this.userSession = userSession;
     this.componentUpdater = componentUpdater;
     this.gitHubSettings = gitHubSettings;
+    this.githubPermissionConverter = githubPermissionConverter;
   }
 
   @Override
@@ -103,7 +114,7 @@ public class GitHubDevOpsPlatformService implements DevOpsPlatformService {
   public Optional<AlmSettingDto> getValidAlmSettingDto(DbSession dbSession, DevOpsProjectDescriptor devOpsProjectDescriptor) {
     Optional<AlmSettingDto> configurationToUse = dbClient.almSettingDao().selectByAlm(dbSession, getDevOpsPlatform()).stream()
       .filter(almSettingDto -> devOpsProjectDescriptor.url().equals(almSettingDto.getUrl()))
-      .filter(almSettingDto -> findInstallationIdToAccessRepo(almSettingDto, devOpsProjectDescriptor.projectIdentifier()).isPresent())
+      .filter(almSettingDto -> findInstallationIdToAccessRepo(devOpsProjectDescriptor, almSettingDto))
       .findFirst();
     if (configurationToUse.isPresent()) {
       LOG.info("DevOps configuration {} auto-detected", configurationToUse.get().getKey());
@@ -114,11 +125,81 @@ public class GitHubDevOpsPlatformService implements DevOpsPlatformService {
     return configurationToUse;
   }
 
+  private boolean findInstallationIdToAccessRepo(DevOpsProjectDescriptor devOpsProjectDescriptor, AlmSettingDto almSettingDto) {
+    GithubAppConfiguration githubAppConfiguration = githubGlobalSettingsValidator.validate(almSettingDto);
+    return findInstallationIdToAccessRepo(almSettingDto, githubAppConfiguration, devOpsProjectDescriptor.projectIdentifier()).isPresent();
+  }
+
+  @Override
+  public boolean isScanAllowedUsingPermissionsFromDevopsPlatform(AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor) {
+    GithubAppConfiguration githubAppConfiguration = githubGlobalSettingsValidator.validate(almSettingDto);
+    long installationId = findInstallationIdToAccessRepo(almSettingDto, githubAppConfiguration, devOpsProjectDescriptor.projectIdentifier())
+      .orElseThrow(() -> new IllegalStateException(format("Impossible to find the repository %s on GitHub, using the devops config %s.",
+        devOpsProjectDescriptor.projectIdentifier(), almSettingDto.getKey())));
+    return isScanAllowedUsingPermissionsFromGithub(devOpsProjectDescriptor.projectIdentifier(), githubAppConfiguration, installationId);
+  }
+
+  private boolean isScanAllowedUsingPermissionsFromGithub(String organizationAndRepository, GithubAppConfiguration githubAppConfiguration, long installationId) {
+    AppInstallationToken accessToken = generateAppInstallationToken(githubAppConfiguration, installationId);
+
+    String[] orgaAndRepoTokenified = organizationAndRepository.split("/");
+    String organization = orgaAndRepoTokenified[0];
+    String repository = orgaAndRepoTokenified[1];
+
+    Set<GithubPermissionsMappingDto> permissionsMappingDtos = dbClient.githubPermissionsMappingDao().findAll(dbClient.openSession(false));
+
+    boolean userHasDirectAccessToRepo = doesUserHaveScanPermission(githubAppConfiguration.getApiEndpoint(), accessToken, organization, repository, permissionsMappingDtos);
+    if (userHasDirectAccessToRepo) {
+      return true;
+    }
+    return doesUserBelongToAGroupWithScanPermission(githubAppConfiguration.getApiEndpoint(), accessToken, organization, repository, permissionsMappingDtos);
+  }
+
+  private boolean doesUserHaveScanPermission(String apiEndpoint, AppInstallationToken accessToken, String organization, String repository,
+    Set<GithubPermissionsMappingDto> permissionsMappingDtos) {
+    Set<GsonRepositoryCollaborator> repositoryCollaborators = githubApplicationClient.getRepositoryCollaborators(apiEndpoint, accessToken, organization, repository);
+
+    String externalLogin = userSession.getExternalIdentity().map(UserSession.ExternalIdentity::login).orElse(null);
+    if (externalLogin == null) {
+      return false;
+    }
+    return repositoryCollaborators.stream()
+      .filter(gsonRepositoryCollaborator -> externalLogin.equals(gsonRepositoryCollaborator.name()))
+      .findAny()
+      .map(gsonRepositoryCollaborator -> hasScanPermission(permissionsMappingDtos, gsonRepositoryCollaborator.roleName(), gsonRepositoryCollaborator.permissions()))
+      .orElse(false);
+  }
+
+  private boolean doesUserBelongToAGroupWithScanPermission(String apiUrl, AppInstallationToken accessToken, String organization, String repository,
+    Set<GithubPermissionsMappingDto> permissionsMappingDtos) {
+    Set<GsonRepositoryTeam> repositoryTeams = githubApplicationClient.getRepositoryTeams(apiUrl, accessToken, organization, repository);
+
+    Set<String> groupsOfUser = findUserMembershipOnSonarQube(organization);
+    return repositoryTeams.stream()
+      .filter(team -> hasScanPermission(permissionsMappingDtos, team.permission(), team.permissions()))
+      .map(GsonRepositoryTeam::name)
+      .anyMatch(groupsOfUser::contains);
+  }
+
+  private Set<String> findUserMembershipOnSonarQube(String organization) {
+    return userSession.getGroups().stream()
+      .map(GroupDto::getName)
+      .filter(groupName -> groupName.contains("/"))
+      .map(name -> name.replaceFirst(organization + "/", ""))
+      .collect(Collectors.toSet());
+  }
+
+  private boolean hasScanPermission(Set<GithubPermissionsMappingDto> permissionsMappingDtos, String role, GsonRepositoryPermissions permissions) {
+    Set<String> sonarqubePermissions = githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(permissionsMappingDtos,
+      role, permissions);
+    return sonarqubePermissions.contains(UserRole.SCAN);
+  }
+
   @Override
   public ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, String projectKey, AlmSettingDto almSettingDto,
     DevOpsProjectDescriptor devOpsProjectDescriptor) {
     GithubAppConfiguration githubAppConfiguration = githubGlobalSettingsValidator.validate(almSettingDto);
-    GithubApplicationClient.Repository repository = findInstallationIdToAccessRepo(almSettingDto, devOpsProjectDescriptor.projectIdentifier())
+    GithubApplicationClient.Repository repository = findInstallationIdToAccessRepo(almSettingDto, githubAppConfiguration, devOpsProjectDescriptor.projectIdentifier())
       .flatMap(installationId -> findRepositoryOnGithub(devOpsProjectDescriptor.projectIdentifier(), githubAppConfiguration, installationId))
       .orElseThrow(() -> new IllegalStateException(format("Impossible to find the repository %s on GitHub, using the devops config %s.",
         devOpsProjectDescriptor.projectIdentifier(), almSettingDto.getKey())));
@@ -126,9 +207,8 @@ public class GitHubDevOpsPlatformService implements DevOpsPlatformService {
     return createProjectAndBindToDevOpsPlatform(dbSession, projectKey, almSettingDto, repository, SCANNER_API_DEVOPS_AUTO_CONFIG);
   }
 
-  private Optional<Long> findInstallationIdToAccessRepo(AlmSettingDto almSettingDto, String repositoryKey) {
+  private Optional<Long> findInstallationIdToAccessRepo(AlmSettingDto almSettingDto, GithubAppConfiguration githubAppConfiguration, String repositoryKey) {
     try {
-      GithubAppConfiguration githubAppConfiguration = githubGlobalSettingsValidator.validate(almSettingDto);
       return githubApplicationClient.getInstallationId(githubAppConfiguration, repositoryKey);
     } catch (Exception exception) {
       LOG.info(format("Could not use DevOps configuration '%s' to access repo %s. Error: %s", almSettingDto.getKey(), repositoryKey, exception.getMessage()));
index 348c8a1615729443202e98fa5facf57997f173b9..9807ed236daf87c5e3578bbdc9803f1a9c9baee0 100644 (file)
@@ -24,6 +24,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.resources.Scopes;
@@ -154,7 +155,7 @@ public class ReportSubmitter {
       // Project key is already used as a module of another project
       ComponentDto anotherBaseProject = dbClient.componentDao().selectOrFailByUuid(dbSession, component.branchUuid());
       errors.add(format("The project '%s' is already defined in SonarQube but as a module of project '%s'. "
-        + "If you really want to stop directly analysing project '%s', please first delete it from SonarQube and then relaunch the analysis of project '%s'.",
+                        + "If you really want to stop directly analysing project '%s', please first delete it from SonarQube and then relaunch the analysis of project '%s'.",
         rawProjectKey, anotherBaseProject.getKey(), anotherBaseProject.getKey(), rawProjectKey));
     }
     if (!errors.isEmpty()) {
@@ -166,21 +167,47 @@ public class ReportSubmitter {
     DbSession dbSession, BranchSupport.ComponentKey componentKey) {
     userSession.checkPermission(GlobalPermission.PROVISION_PROJECTS);
 
-    boolean wouldCurrentUserHaveScanPermission = permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(dbSession, userSession.getUuid(), projectKey);
-    if (!wouldCurrentUserHaveScanPermission) {
-      throw insufficientPrivilegesException();
+    DevOpsProjectDescriptor devOpsProjectDescriptor = devOpsPlatformService.getDevOpsProjectDescriptor(characteristics).orElse(null);
+    AlmSettingDto almSettingDto = getAlmSettingDto(dbSession, devOpsProjectDescriptor);
+
+    throwIfNoValidDevOpsConfigurationFoundForDevOpsProject(devOpsProjectDescriptor, almSettingDto);
+    throwIfCurrentUserWouldNotHaveScanPermission(projectKey, dbSession, devOpsProjectDescriptor, almSettingDto);
+
+    if (almSettingDto != null) {
+      return devOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, projectKey, almSettingDto, devOpsProjectDescriptor);
     }
+    return createProject(dbSession, componentKey.getKey(), defaultIfBlank(projectName, projectKey));
+  }
 
-    if (managedInstanceService.isInstanceExternallyManaged()) {
-      return createProject(dbSession, componentKey.getKey(), defaultIfBlank(projectName, projectKey));
+  @CheckForNull
+  private AlmSettingDto getAlmSettingDto(DbSession dbSession, @Nullable DevOpsProjectDescriptor devOpsProjectDescriptor) {
+    if (devOpsProjectDescriptor != null) {
+      return devOpsPlatformService.getValidAlmSettingDto(dbSession, devOpsProjectDescriptor).orElse(null);
     }
+    return null;
+  }
 
-    Optional<DevOpsProjectDescriptor> devOpsProjectDescriptor = devOpsPlatformService.getDevOpsProjectDescriptor(characteristics);
-    Optional<AlmSettingDto> almSettingDto = devOpsProjectDescriptor.flatMap(descriptor -> devOpsPlatformService.getValidAlmSettingDto(dbSession, descriptor));
-    if (almSettingDto.isPresent()) {
-      return devOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, projectKey, almSettingDto.get(), devOpsProjectDescriptor.get());
+  private static void throwIfNoValidDevOpsConfigurationFoundForDevOpsProject(@Nullable DevOpsProjectDescriptor devOpsProjectDescriptor, @Nullable AlmSettingDto almSettingDto) {
+    if (devOpsProjectDescriptor != null && almSettingDto == null) {
+      throw new IllegalArgumentException(format("The project %s could not be created. It was auto-detected as a %s project "
+                                                + "and no valid DevOps platform configuration were found to access %s",
+        devOpsProjectDescriptor.projectIdentifier(), devOpsProjectDescriptor.alm(), devOpsProjectDescriptor.url()));
     }
-    return createProject(dbSession, componentKey.getKey(), defaultIfBlank(projectName, projectKey));
+  }
+
+  private void throwIfCurrentUserWouldNotHaveScanPermission(String projectKey, DbSession dbSession, @Nullable DevOpsProjectDescriptor devOpsProjectDescriptor,
+    @Nullable AlmSettingDto almSettingDto) {
+    if (!wouldCurrentUserHaveScanPermission(projectKey, dbSession, devOpsProjectDescriptor, almSettingDto)) {
+      throw insufficientPrivilegesException();
+    }
+  }
+
+  private boolean wouldCurrentUserHaveScanPermission(String projectKey, DbSession dbSession, @Nullable DevOpsProjectDescriptor devOpsProjectDescriptor,
+    @Nullable AlmSettingDto almSettingDto) {
+    if (managedInstanceService.isInstanceExternallyManaged() && almSettingDto != null && devOpsProjectDescriptor != null) {
+      return devOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, devOpsProjectDescriptor);
+    }
+    return permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(dbSession, userSession.getUuid(), projectKey);
   }
 
   private ComponentCreationData createProject(DbSession dbSession, String projectKey, String projectName) {
index 0a7e7460cf33a3f71c769913d373cfb421cbd9c7..cda5b3809e8f299f7478a32a741ea499f7152f94 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.db.alm.setting.AlmSettingDto;
 
 import static java.util.Collections.emptySet;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
@@ -86,6 +87,26 @@ public class DelegatingDevOpsPlatformServiceTest {
       .usingRecursiveComparison().isEqualTo(new AlmSettingDto().setAlm(ALM.GITHUB));
   }
 
+  @Test
+  public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenNoDelegates_shouldThrow() {
+    DevOpsProjectDescriptor devOpsProjectDescriptor = mock();
+    when(devOpsProjectDescriptor.alm()).thenReturn(ALM.GITHUB);
+
+    assertThatIllegalStateException()
+      .isThrownBy(() -> NO_DEVOPS_PLATFORMS.isScanAllowedUsingPermissionsFromDevopsPlatform(mock(), devOpsProjectDescriptor))
+      .withMessage("No delegate found to handle projects on GITHUB");
+  }
+
+  @Test
+  public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenDelegates_shouldReturnDelegateResponse() {
+    DevOpsProjectDescriptor devOpsProjectDescriptor = mock();
+    when(devOpsProjectDescriptor.alm()).thenReturn(ALM.GITHUB);
+
+    boolean isScanAllowed = MULTIPLE_DEVOPS_PLATFORMS.isScanAllowedUsingPermissionsFromDevopsPlatform(mock(), devOpsProjectDescriptor);
+
+    assertThat(isScanAllowed).isTrue();
+  }
+
   private static DevOpsPlatformService mockGitHubDevOpsPlatformService() {
     DevOpsPlatformService mockDevOpsPlatformService = mock();
     when(mockDevOpsPlatformService.getDevOpsPlatform()).thenReturn(ALM.GITHUB);
@@ -93,6 +114,7 @@ public class DelegatingDevOpsPlatformServiceTest {
       .thenReturn(Optional.of(new DevOpsProjectDescriptor(ALM.GITHUB, "githubUrl", "githubRepo")));
     when(mockDevOpsPlatformService.getValidAlmSettingDto(any(), any()))
       .thenReturn(Optional.of(new AlmSettingDto().setAlm(ALM.GITHUB)));
+    when(mockDevOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(any(), any())).thenReturn(true);
     return mockDevOpsPlatformService;
   }
 
index db7328ae4077399b5b7f758685b32b730dc83da9..0820c280f69a2a9f4fb597e37f653669ca444e1e 100644 (file)
  */
 package org.sonar.server.almsettings.ws;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -35,11 +37,16 @@ import org.mockito.junit.MockitoJUnitRunner;
 import org.sonar.alm.client.github.AppInstallationToken;
 import org.sonar.alm.client.github.GithubApplicationClient;
 import org.sonar.alm.client.github.GithubGlobalSettingsValidator;
+import org.sonar.alm.client.github.api.GsonRepositoryCollaborator;
+import org.sonar.alm.client.github.api.GsonRepositoryTeam;
 import org.sonar.alm.client.github.config.GithubAppConfiguration;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.testfixtures.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.api.web.UserRole;
 import org.sonar.auth.github.GitHubSettings;
+import org.sonar.auth.github.GithubPermissionConverter;
+import org.sonar.auth.github.GsonRepositoryPermissions;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.alm.setting.ALM;
@@ -48,6 +55,8 @@ import org.sonar.db.alm.setting.ProjectAlmSettingDao;
 import org.sonar.db.alm.setting.ProjectAlmSettingDto;
 import org.sonar.db.project.CreationMethod;
 import org.sonar.db.project.ProjectDto;
+import org.sonar.db.provisioning.GithubPermissionsMappingDto;
+import org.sonar.db.user.GroupDto;
 import org.sonar.server.almintegration.ws.ProjectKeyGenerator;
 import org.sonar.server.component.ComponentCreationData;
 import org.sonar.server.component.ComponentCreationParameters;
@@ -58,6 +67,7 @@ import org.sonar.server.project.Visibility;
 import org.sonar.server.user.UserSession;
 
 import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.toSet;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
 import static org.mockito.ArgumentMatchers.any;
@@ -84,6 +94,7 @@ public class GitHubDevOpsPlatformServiceTest {
   private static final String ORGANIZATION_NAME = "orgname";
   private static final String GITHUB_REPO_FULL_NAME = ORGANIZATION_NAME + "/" + PROJECT_NAME;
   private static final String GITHUB_API_URL = "https://api.toto.com";
+  private static final DevOpsProjectDescriptor DEV_OPS_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITHUB, GITHUB_API_URL, GITHUB_REPO_FULL_NAME);
 
   @Mock
   private DbSession dbSession;
@@ -109,6 +120,9 @@ public class GitHubDevOpsPlatformServiceTest {
   @Mock
   private GitHubSettings gitHubSettings;
 
+  @Mock
+  private GithubPermissionConverter githubPermissionConverter;
+
   @InjectMocks
   private GitHubDevOpsPlatformService gitHubDevOpsPlatformService;
 
@@ -179,23 +193,20 @@ public class GitHubDevOpsPlatformServiceTest {
 
   @Test
   public void createProjectAndBindToDevOpsPlatform_whenRepoNotFound_throws() {
-    DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, GITHUB_API_URL, GITHUB_REPO_FULL_NAME);
 
-    AlmSettingDto almSettingDto = mockAlmSettingDto(devOpsProjectDescriptor);
+    AlmSettingDto almSettingDto = mockAlmSettingDto();
     GithubAppConfiguration githubAppConfiguration = mockGitHubAppConfiguration(almSettingDto);
     when(githubApplicationClient.getInstallationId(githubAppConfiguration, GITHUB_REPO_FULL_NAME)).thenReturn(Optional.empty());
 
     assertThatIllegalStateException().isThrownBy(
-        () -> gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, PROJECT_KEY, almSettingDto, devOpsProjectDescriptor))
+        () -> gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, PROJECT_KEY, almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR))
       .withMessage("Impossible to find the repository orgname/projectName on GitHub, using the devops config devops-platform-config-1.");
   }
 
   @Test
   public void createProjectAndBindToDevOpsPlatform_whenRepoFoundOnGitHub_successfullyCreatesProject() {
     // given
-    DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, GITHUB_API_URL, GITHUB_REPO_FULL_NAME);
-
-    AlmSettingDto almSettingDto = mockAlmSettingDto(devOpsProjectDescriptor);
+    AlmSettingDto almSettingDto = mockAlmSettingDto();
     mockExistingGitHubRepository(almSettingDto);
 
     ComponentCreationData componentCreationData = mockProjectCreation();
@@ -204,7 +215,7 @@ public class GitHubDevOpsPlatformServiceTest {
 
     // when
     ComponentCreationData actualComponentCreationData = gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, PROJECT_KEY, almSettingDto,
-      devOpsProjectDescriptor);
+      DEV_OPS_PROJECT_DESCRIPTOR);
 
     // then
     assertThat(actualComponentCreationData).isEqualTo(componentCreationData);
@@ -224,11 +235,10 @@ public class GitHubDevOpsPlatformServiceTest {
   @Test
   public void createProjectAndBindToDevOpsPlatform_whenRepoFoundOnGitHubAndAutoProvisioningOn_successfullyCreatesProject() {
     // given
-    DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, GITHUB_API_URL, GITHUB_REPO_FULL_NAME);
     when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE);
     when(gitHubSettings.isProvisioningEnabled()).thenReturn(true);
 
-    AlmSettingDto almSettingDto = mockAlmSettingDto(devOpsProjectDescriptor);
+    AlmSettingDto almSettingDto = mockAlmSettingDto();
     mockExistingGitHubRepository(almSettingDto);
 
     ComponentCreationData componentCreationData = mockProjectCreation();
@@ -237,7 +247,7 @@ public class GitHubDevOpsPlatformServiceTest {
 
     // when
     ComponentCreationData actualComponentCreationData = gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, PROJECT_KEY, almSettingDto,
-      devOpsProjectDescriptor);
+      DEV_OPS_PROJECT_DESCRIPTOR);
 
     // then
     assertThat(actualComponentCreationData).isEqualTo(componentCreationData);
@@ -256,17 +266,210 @@ public class GitHubDevOpsPlatformServiceTest {
 
   @Test
   public void createProjectAndBindToDevOpsPlatform_whenWrongToken_throws() {
-    DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, GITHUB_API_URL, GITHUB_REPO_FULL_NAME);
-    AlmSettingDto almSettingDto = mockAlmSettingDto(devOpsProjectDescriptor);
+    AlmSettingDto almSettingDto = mockAlmSettingDto();
     mockExistingGitHubRepository(almSettingDto);
 
     when(githubApplicationClient.createAppInstallationToken(any(), anyLong())).thenReturn(Optional.empty());
 
     assertThatIllegalStateException().isThrownBy(
-        () -> gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, PROJECT_KEY, almSettingDto, devOpsProjectDescriptor))
+        () -> gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, PROJECT_KEY, almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR))
       .withMessage("Error while generating token for GitHub Api Url https://api.toto.com (installation id: 534534534543)");
   }
 
+  @Test
+  public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenUserIsCollaboratorWithScan_returnsTrue() {
+    // given
+    AlmSettingDto almSettingDto = mockAlmSettingDto();
+
+    GsonRepositoryCollaborator collaborator1 = mockCollaborator("collaborator1", 1, "role1");
+    GsonRepositoryCollaborator collaborator2 = mockCollaborator("collaborator2", 2, "role2");
+    mockGithubCollaboratorsFromApi(almSettingDto, collaborator1, collaborator2);
+    bindSessionToCollaborator(collaborator2);
+
+    mockPermissionsConversion(collaborator2, "another_perm", UserRole.SCAN);
+
+    // when
+    boolean isGranted = gitHubDevOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR);
+
+    // then
+    assertThat(isGranted).isTrue();
+  }
+
+  @Test
+  public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenUserIsCollaboratorButWithoutScan_returnsFalse() {
+    // given
+    AlmSettingDto almSettingDto = mockAlmSettingDto();
+
+    GsonRepositoryCollaborator collaborator1 = mockCollaborator("collaborator1", 1, "role1");
+    mockGithubCollaboratorsFromApi(almSettingDto, collaborator1);
+    bindSessionToCollaborator(collaborator1);
+
+    mockPermissionsConversion(collaborator1, "admin");
+
+    // when
+    boolean isGranted = gitHubDevOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR);
+
+    // then
+    assertThat(isGranted).isFalse();
+  }
+
+  @Test
+  public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenRepoFoundOnGitHubAndUserIsCollaboratorButWithoutScan_returnsFalse() {
+    // given
+    AlmSettingDto almSettingDto = mockAlmSettingDto();
+
+    GsonRepositoryCollaborator collaborator1 = mockCollaborator("collaborator1", 1, "role1");
+    mockGithubCollaboratorsFromApi(almSettingDto, collaborator1);
+    bindSessionToCollaborator(collaborator1);
+
+    mockPermissionsConversion(collaborator1, "admin");
+
+    // when
+    boolean isGranted = gitHubDevOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR);
+
+    // then
+    assertThat(isGranted).isFalse();
+  }
+
+  @Test
+  public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenRepoFoundOnGitHubAndUserIsNotExternal_returnsFalse() {
+    // given
+    AlmSettingDto almSettingDto = mockAlmSettingDto();
+
+    GsonRepositoryCollaborator collaborator1 = mockCollaborator("collaborator1", 1, "role1");
+    mockGithubCollaboratorsFromApi(almSettingDto, collaborator1);
+    mockPermissionsConversion(collaborator1, "admin");
+
+    // when
+    boolean isGranted = gitHubDevOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR);
+
+    // then
+    assertThat(isGranted).isFalse();
+  }
+
+  private void mockPermissionsConversion(GsonRepositoryCollaborator collaborator, String... sqPermissions) {
+    Set<GithubPermissionsMappingDto> githubPermissionsMappingDtos = mockPermissionsMappingsDtos();
+    when(githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(githubPermissionsMappingDtos, collaborator.roleName(), collaborator.permissions()))
+      .thenReturn(Arrays.stream(sqPermissions).collect(toSet()));
+  }
+
+  private void bindSessionToCollaborator(GsonRepositoryCollaborator collaborator1) {
+    UserSession.ExternalIdentity externalIdentity = new UserSession.ExternalIdentity(String.valueOf(collaborator1.id()), collaborator1.name());
+    when(userSession.getExternalIdentity()).thenReturn(Optional.of(externalIdentity));
+  }
+
+  private static GsonRepositoryCollaborator mockCollaborator(String collaborator1, int id, String role1) {
+    return new GsonRepositoryCollaborator(collaborator1, id, role1,
+      new GsonRepositoryPermissions(false, false, false, false, false));
+  }
+
+  private void mockGithubCollaboratorsFromApi(AlmSettingDto almSettingDto, GsonRepositoryCollaborator... repositoryCollaborators) {
+    GithubAppConfiguration githubAppConfiguration = mockGitHubAppConfiguration(almSettingDto);
+    when(githubApplicationClient.getInstallationId(githubAppConfiguration, GITHUB_REPO_FULL_NAME)).thenReturn(Optional.of(APP_INSTALLATION_ID));
+    AppInstallationToken appInstallationToken = mockAppInstallationToken(githubAppConfiguration, APP_INSTALLATION_ID);
+    when(githubApplicationClient.getRepositoryCollaborators(GITHUB_API_URL, appInstallationToken, ORGANIZATION_NAME, PROJECT_NAME))
+      .thenReturn(Arrays.stream(repositoryCollaborators).collect(toSet()));
+  }
+
+  @Test
+  public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenInstallationIdNotFound_throws() {
+    // given
+    AlmSettingDto almSettingDto = mockAlmSettingDto();
+
+    when(githubApplicationClient.getInstallationId(any(), any())).thenReturn(Optional.empty());
+
+    // when
+    assertThatIllegalStateException()
+      .isThrownBy(() -> gitHubDevOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR))
+        .withMessage("Impossible to find the repository orgname/projectName on GitHub, using the devops config devops-platform-config-1.");
+  }
+
+  @Test
+  public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenUserIsMemberOfAGroupWithScan_returnsTrue() {
+    // given
+    AlmSettingDto almSettingDto = mockAlmSettingDto();
+
+    GsonRepositoryTeam team1 = mockGithubTeam("team1", 1, "role1");
+    GsonRepositoryTeam team2 = mockGithubTeam("team2", 2, "role2");
+    mockTeamsFromApi(almSettingDto, team1, team2);
+    bindGroupsToUser(team1.name(), team2.name());
+
+    mockPermissionsConversion(team1, "another_perm", UserRole.SCAN);
+
+    // when
+    boolean isGranted = gitHubDevOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR);
+
+    // then
+    assertThat(isGranted).isTrue();
+  }
+
+  @Test
+  public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenUserIsMemberOfAGroupWithoutScanPermission_returnsFalse() {
+    // given
+    AlmSettingDto almSettingDto = mockAlmSettingDto();
+
+    GsonRepositoryTeam team1 = mockGithubTeam("team1", 1, "role1");
+    mockTeamsFromApi(almSettingDto, team1);
+    bindGroupsToUser(team1.name(), "another_local_team");
+
+    mockPermissionsConversion(team1, "another_perm", "admin");
+
+    // when
+    boolean isGranted = gitHubDevOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR);
+
+    // then
+    assertThat(isGranted).isFalse();
+  }
+
+  @Test
+  public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenUserIsNotMemberOfAGroupWithScanPermission_returnsFalse() {
+    // given
+    AlmSettingDto almSettingDto = mockAlmSettingDto();
+
+    GsonRepositoryTeam team1 = mockGithubTeam("team1", 1, "role1");
+    mockTeamsFromApi(almSettingDto, team1);
+    bindGroupsToUser("another_local_team");
+
+    mockPermissionsConversion(team1, "another_perm", UserRole.SCAN);
+
+    // when
+    boolean isGranted = gitHubDevOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR);
+
+    // then
+    assertThat(isGranted).isFalse();
+  }
+
+  private void bindGroupsToUser(String... groupNames) {
+    Set<GroupDto> groupDtos = Arrays.stream(groupNames)
+      .map(groupName -> new GroupDto().setName(ORGANIZATION_NAME + "/" + groupName).setUuid("uuid_" + groupName))
+      .collect(toSet());
+    when(userSession.getGroups()).thenReturn(groupDtos);
+  }
+
+  private static GsonRepositoryTeam mockGithubTeam(String name, int id, String role) {
+    return new GsonRepositoryTeam(name, id, name + "slug", role, new GsonRepositoryPermissions(false, false, false, false, false));
+  }
+
+  private void mockTeamsFromApi(AlmSettingDto almSettingDto, GsonRepositoryTeam... repositoryTeams) {
+    GithubAppConfiguration githubAppConfiguration = mockGitHubAppConfiguration(almSettingDto);
+    when(githubApplicationClient.getInstallationId(githubAppConfiguration, GITHUB_REPO_FULL_NAME)).thenReturn(Optional.of(APP_INSTALLATION_ID));
+    AppInstallationToken appInstallationToken = mockAppInstallationToken(githubAppConfiguration, APP_INSTALLATION_ID);
+    when(githubApplicationClient.getRepositoryTeams(GITHUB_API_URL, appInstallationToken, ORGANIZATION_NAME, PROJECT_NAME))
+      .thenReturn(Arrays.stream(repositoryTeams).collect(toSet()));
+  }
+
+  private void mockPermissionsConversion(GsonRepositoryTeam team, String... sqPermissions) {
+    Set<GithubPermissionsMappingDto> githubPermissionsMappingDtos = mockPermissionsMappingsDtos();
+    when(githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(githubPermissionsMappingDtos, team.permission(), team.permissions()))
+      .thenReturn(Arrays.stream(sqPermissions).collect(toSet()));
+  }
+
+  private Set<GithubPermissionsMappingDto> mockPermissionsMappingsDtos() {
+    Set<GithubPermissionsMappingDto> githubPermissionsMappingDtos = Set.of(mock(GithubPermissionsMappingDto.class));
+    when(dbClient.githubPermissionsMappingDao().findAll(any())).thenReturn(githubPermissionsMappingDtos);
+    return githubPermissionsMappingDtos;
+  }
+
   private void mockExistingGitHubRepository(AlmSettingDto almSettingDto) {
     GithubAppConfiguration githubAppConfiguration = mockGitHubAppConfiguration(almSettingDto);
     when(githubApplicationClient.getInstallationId(githubAppConfiguration, GITHUB_REPO_FULL_NAME)).thenReturn(Optional.of(APP_INSTALLATION_ID));
@@ -296,7 +499,7 @@ public class GitHubDevOpsPlatformServiceTest {
     return appInstallationToken;
   }
 
-  private static AlmSettingDto mockAlmSettingDto(DevOpsProjectDescriptor devOpsProjectDescriptor) {
+  private static AlmSettingDto mockAlmSettingDto() {
     AlmSettingDto almSettingDto = mock();
     when(almSettingDto.getUuid()).thenReturn("almsetting-uuid-1");
     when(almSettingDto.getKey()).thenReturn("devops-platform-config-1");
index 097e0d363d583dd1ddb90650e434cfe0380756dd..4f9c29e96b3763532071d94ef57eafdf8b73775e 100644 (file)
@@ -41,6 +41,7 @@ import org.sonar.api.server.rule.RulesDefinitionXmlLoader;
 import org.sonar.auth.bitbucket.BitbucketModule;
 import org.sonar.auth.github.GitHubModule;
 import org.sonar.auth.github.GitHubSettings;
+import org.sonar.auth.github.GithubPermissionConverter;
 import org.sonar.auth.gitlab.GitLabModule;
 import org.sonar.auth.ldap.LdapModule;
 import org.sonar.auth.saml.SamlModule;
@@ -552,6 +553,7 @@ public class PlatformLevel4 extends PlatformLevel {
       GithubProvisioningConfigValidator.class,
       GithubProvisioningWs.class,
       GitHubDevOpsPlatformService.class,
+      GithubPermissionConverter.class,
       BitbucketCloudRestClientConfiguration.class,
       BitbucketServerRestClient.class,
       GitlabHttpClient.class,