]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20700 Created DevOpsProjectCreator and DevOpsProjectCreatorInteface to better...
authorAurelien Poscia <aurelien.poscia@sonarsource.com>
Wed, 18 Oct 2023 09:20:59 +0000 (11:20 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 20 Oct 2023 20:02:40 +0000 (20:02 +0000)
22 files changed:
server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClient.java
server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClientImpl.java
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/almintegration/ws/github/ImportGithubProjectAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DelegatingDevOpsPlatformService.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DelegatingDevOpsProjectCreatorFactory.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsPlatformService.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectCreator.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectCreatorFactory.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GitHubDevOpsPlatformService.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreationParameters.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreator.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactory.java [new file with mode: 0644]
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 [deleted file]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/DelegatingDevOpsProjectCreatorFactoryTest.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GitHubDevOpsPlatformServiceTest.java [deleted file]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactoryTest.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorTest.java [new file with mode: 0644]
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java

index 7ef05182cd06fd64f7d3dae8984c598854a701a5..1f766807eecdf2a93029803364ffd2bb1f58a984 100644 (file)
@@ -96,11 +96,9 @@ public interface GithubApplicationClient {
    */
   Optional<Repository> getRepository(String appUrl, AccessToken accessToken, String repositoryKey);
 
+  Set<GsonRepositoryTeam> getRepositoryTeams(String appUrl, AccessToken accessToken, String orgName, String repoName);
 
-
-  Set<GsonRepositoryTeam> getRepositoryTeams(String appUrl, AppInstallationToken accessToken, String orgName, String repoName);
-
-  Set<GsonRepositoryCollaborator> getRepositoryCollaborators(String appUrl, AppInstallationToken accessToken, String orgName, String repoName);
+  Set<GsonRepositoryCollaborator> getRepositoryCollaborators(String appUrl, AccessToken accessToken, String orgName, String repoName);
 
   class Repositories {
     private int total;
index 9403377c59fa297d9aede40f5d52dc9f8e9e751e..477f67107419e8544b456c08beacfa9d5c7921c9 100644 (file)
@@ -369,13 +369,13 @@ public class GithubApplicationClientImpl implements GithubApplicationClient {
   }
 
   @Override
-  public Set<GsonRepositoryTeam> getRepositoryTeams(String appUrl, AppInstallationToken accessToken, String orgName, String repoName) {
+  public Set<GsonRepositoryTeam> getRepositoryTeams(String appUrl, AccessToken accessToken, String orgName, String repoName) {
     return Set
       .copyOf(executePaginatedQuery(appUrl, accessToken, format("/repos/%s/%s/teams", orgName, repoName), resp -> GSON.fromJson(resp, REPOSITORY_TEAM_LIST_TYPE)));
   }
 
   @Override
-  public Set<GsonRepositoryCollaborator> getRepositoryCollaborators(String appUrl, AppInstallationToken accessToken, String orgName, String repoName) {
+  public Set<GsonRepositoryCollaborator> getRepositoryCollaborators(String appUrl, AccessToken accessToken, String orgName, String repoName) {
     return Set
       .copyOf(
         executePaginatedQuery(
index 9ebc0dce37cbf50e5817648d21771929740257c4..78c3c9ec3037b0f45bf085769c98602a8a038645 100644 (file)
@@ -49,7 +49,7 @@ import org.sonar.db.project.ProjectDto;
 import org.sonar.db.user.UserDto;
 import org.sonar.server.almintegration.ws.ImportHelper;
 import org.sonar.server.almintegration.ws.ProjectKeyGenerator;
-import org.sonar.server.almsettings.ws.GitHubDevOpsPlatformService;
+import org.sonar.server.almsettings.ws.GithubProjectCreatorFactory;
 import org.sonar.server.component.ComponentUpdater;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.es.IndexersImpl;
@@ -132,10 +132,10 @@ public class ImportGithubProjectActionIT {
   private final ManagedProjectService managedProjectService = mock(ManagedProjectService.class);
 
   private final GithubPermissionConverter githubPermissionConverter = new GithubPermissionConverter();
-  private final GitHubDevOpsPlatformService gitHubDevOpsPlatformService = new GitHubDevOpsPlatformService(db.getDbClient(),
+  private final GithubProjectCreatorFactory gitHubProjectCreatorFactory = new GithubProjectCreatorFactory(db.getDbClient(),
     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));
+    componentUpdater, importHelper, newCodeDefinitionResolver, defaultBranchNameResolver, gitHubProjectCreatorFactory));
 
   @Before
   public void before() {
index 4bcbd73524cda1e41a5b45de400e7fdac7faa52c..1e8e4223b1ab05594606b4ce36fc047ec6bdf595 100644 (file)
@@ -47,8 +47,8 @@ import org.sonar.db.component.ProjectData;
 import org.sonar.db.project.CreationMethod;
 import org.sonar.db.project.ProjectDto;
 import org.sonar.db.user.UserDto;
-import org.sonar.server.almsettings.ws.DevOpsPlatformService;
-import org.sonar.server.almsettings.ws.GitHubDevOpsPlatformService;
+import org.sonar.server.almsettings.ws.DevOpsProjectCreatorFactory;
+import org.sonar.server.almsettings.ws.GithubProjectCreatorFactory;
 import org.sonar.server.component.ComponentCreationData;
 import org.sonar.server.component.ComponentCreationParameters;
 import org.sonar.server.component.ComponentUpdater;
@@ -107,12 +107,12 @@ public class BranchReportSubmitterIT {
   private final BranchSupportDelegate branchSupportDelegate = mock(BranchSupportDelegate.class);
   private final BranchSupport branchSupport = spy(new BranchSupport(branchSupportDelegate));
 
-  private final DevOpsPlatformService devOpsPlatformService = new GitHubDevOpsPlatformService(db.getDbClient(), null,
+  private final DevOpsProjectCreatorFactory devOpsProjectCreatorFactory = new GithubProjectCreatorFactory(db.getDbClient(), 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,
-    projectDefaultVisibility, devOpsPlatformService, managedInstanceService);
+    projectDefaultVisibility, devOpsProjectCreatorFactory, managedInstanceService);
 
   @Before
   public void before() {
index fd1cf3fb2e03edfb8ae4862b8a04693dc3f3ec62..dd71c29014fced69bf5eaba9bf7b13e932219425 100644 (file)
@@ -20,7 +20,6 @@
 package org.sonar.server.ce.queue;
 
 import java.io.InputStream;
-import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -28,11 +27,8 @@ import org.apache.commons.io.IOUtils;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-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.config.GithubAppConfiguration;
-import org.sonar.alm.client.github.config.GithubAppInstallation;
 import org.sonar.api.utils.System2;
 import org.sonar.auth.github.GitHubSettings;
 import org.sonar.ce.queue.CeQueue;
@@ -53,10 +49,13 @@ import org.sonar.db.project.CreationMethod;
 import org.sonar.db.project.ProjectDto;
 import org.sonar.db.user.UserDto;
 import org.sonar.server.almintegration.ws.ProjectKeyGenerator;
-import org.sonar.server.almsettings.ws.DelegatingDevOpsPlatformService;
-import org.sonar.server.almsettings.ws.DevOpsPlatformService;
+import org.sonar.server.almsettings.ws.DelegatingDevOpsProjectCreatorFactory;
+import org.sonar.server.almsettings.ws.DevOpsProjectCreator;
+import org.sonar.server.almsettings.ws.DevOpsProjectCreatorFactory;
 import org.sonar.server.almsettings.ws.DevOpsProjectDescriptor;
-import org.sonar.server.almsettings.ws.GitHubDevOpsPlatformService;
+import org.sonar.server.almsettings.ws.GithubProjectCreator;
+import org.sonar.server.almsettings.ws.GithubProjectCreationParameters;
+import org.sonar.server.almsettings.ws.GithubProjectCreatorFactory;
 import org.sonar.server.component.ComponentUpdater;
 import org.sonar.server.es.TestIndexers;
 import org.sonar.server.exceptions.BadRequestException;
@@ -76,10 +75,8 @@ 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;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
@@ -100,6 +97,7 @@ public class ReportSubmitterIT {
   private static final String PROJECT_UUID = "P1";
   private static final String PROJECT_NAME = "My Project";
   private static final String TASK_UUID = "TASK_1";
+  private static final Map<String, String> CHARACTERISTICS = Map.of("random", "data");
 
   @Rule
   public final UserSessionRule userSession = UserSessionRule.standalone();
@@ -122,16 +120,16 @@ public class ReportSubmitterIT {
   private final GitHubSettings gitHubSettings = mock();
   private final ProjectKeyGenerator projectKeyGenerator = mock();
 
-  private final DevOpsPlatformService devOpsPlatformService = new DelegatingDevOpsPlatformService(
-    Set.of(new GitHubDevOpsPlatformService(db.getDbClient(), githubGlobalSettingsValidator,
-      githubApplicationClient, projectDefaultVisibility, projectKeyGenerator, userSession, componentUpdater, gitHubSettings, null)));
+  private final GithubProjectCreatorFactory githubProjectCreatorFactory = new GithubProjectCreatorFactory(db.getDbClient(), githubGlobalSettingsValidator,
+    githubApplicationClient, projectDefaultVisibility, projectKeyGenerator, userSession, componentUpdater, gitHubSettings, null);
+  private final DevOpsProjectCreatorFactory devOpsProjectCreatorFactory = new DelegatingDevOpsProjectCreatorFactory(Set.of(githubProjectCreatorFactory));
 
-  private final DevOpsPlatformService devOpsPlatformServiceSpy = spy(devOpsPlatformService);
+  private final DevOpsProjectCreatorFactory devOpsProjectCreatorFactorySpy = spy(devOpsProjectCreatorFactory);
 
   private final ManagedInstanceService managedInstanceService = mock();
 
   private final ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentUpdater, permissionTemplateService, db.getDbClient(), ossEditionBranchSupport,
-    projectDefaultVisibility, devOpsPlatformServiceSpy, managedInstanceService);
+    projectDefaultVisibility, devOpsProjectCreatorFactorySpy, managedInstanceService);
 
   @Before
   public void before() {
@@ -275,25 +273,6 @@ public class ReportSubmitterIT {
     assertLocalProjectWasCreated();
   }
 
-  @Test
-  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();
-
-    Map<String, String> characteristics = Map.of("random", "data");
-    DevOpsProjectDescriptor projectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, "apiUrl", "orga/repo");
-    when(devOpsPlatformServiceSpy.getDevOpsProjectDescriptor(characteristics)).thenReturn(Optional.of(projectDescriptor));
-
-    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();
-  }
-
-  private void assertNoProjectWasCreated() {
-    assertThat(db.getDbClient().projectDao().selectAll(db.getSession())).isEmpty();
-  }
 
   @Test
   public void submit_whenReportIsForANewProjectWithValidAlmSettingsAutoProvisioningOnAndPermOnGh_createsProjectWithBinding() {
@@ -302,35 +281,14 @@ public class ReportSubmitterIT {
     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");
-
-    AlmSettingDto almSettingDto = mockInteractionsWithDevOpsPlatformServiceSpyBeforeProjectCreation(characteristics, projectDescriptor);
-    when(devOpsPlatformServiceSpy.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, projectDescriptor)).thenReturn(true);
+    DevOpsProjectCreator devOpsProjectCreator = mockAlmSettingDtoAndDevOpsProjectCreator(CHARACTERISTICS);
+    doReturn(true).when(devOpsProjectCreator).isScanAllowedUsingPermissionsFromDevopsPlatform();
 
-    underTest.submit(PROJECT_KEY, PROJECT_NAME, characteristics, IOUtils.toInputStream("{binary}", UTF_8));
+    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);
@@ -338,9 +296,7 @@ public class ReportSubmitterIT {
     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));
+    underTest.submit(PROJECT_KEY, PROJECT_NAME, CHARACTERISTICS, IOUtils.toInputStream("{binary}", UTF_8));
     assertLocalProjectWasCreated();
   }
 
@@ -358,15 +314,13 @@ public class ReportSubmitterIT {
   @Test
   public void submit_whenReportIsForANewProjectWithValidAlmSettings_createsProjectWithDevOpsBinding() {
     userSession.addPermission(GlobalPermission.SCAN).addPermission(PROVISION_PROJECTS);
-    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");
+    mockAlmSettingDtoAndDevOpsProjectCreator(CHARACTERISTICS);
 
-    mockInteractionsWithDevOpsPlatformServiceSpyBeforeProjectCreation(characteristics, projectDescriptor);
+    when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), any(), eq(PROJECT_KEY))).thenReturn(true);
 
-    underTest.submit(PROJECT_KEY, PROJECT_NAME, characteristics, IOUtils.toInputStream("{binary}", UTF_8));
+    underTest.submit(PROJECT_KEY, PROJECT_NAME, CHARACTERISTICS, IOUtils.toInputStream("{binary}", UTF_8));
 
     assertProjectWasCreatedWithBinding();
   }
@@ -382,32 +336,27 @@ public class ReportSubmitterIT {
     assertThat(db.getDbClient().projectAlmSettingDao().selectByProject(db.getSession(), projectDto.getUuid())).isPresent();
   }
 
-  private AlmSettingDto mockInteractionsWithDevOpsPlatformServiceSpyBeforeProjectCreation(Map<String, String> characteristics, DevOpsProjectDescriptor projectDescriptor) {
-    doReturn(Optional.of(projectDescriptor)).when(devOpsPlatformServiceSpy).getDevOpsProjectDescriptor(characteristics);
+  private DevOpsProjectCreator mockAlmSettingDtoAndDevOpsProjectCreator(Map<String, String> characteristics) {
     AlmSettingDto almSettingDto = mock(AlmSettingDto.class);
     when(almSettingDto.getAlm()).thenReturn(ALM.GITHUB);
     when(almSettingDto.getUrl()).thenReturn("https://www.toto.com");
-    when(almSettingDto.getUuid()).thenReturn("TEST_GH");
-    doReturn(Optional.of(almSettingDto)).when(devOpsPlatformServiceSpy).getValidAlmSettingDto(any(), eq(projectDescriptor));
-    mockGithubInteractions(almSettingDto);
-    return almSettingDto;
+    when(almSettingDto.getUuid()).thenReturn("uuid_gh_1");
+
+    mockGithubRepository();
+
+    DevOpsProjectDescriptor projectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, "apiUrl", "orga/repo");
+    GithubProjectCreationParameters githubProjectCreationParameters = new GithubProjectCreationParameters(projectDescriptor, almSettingDto, mock(), true, managedInstanceService.isInstanceExternallyManaged(), userSession);
+    DevOpsProjectCreator devOpsProjectCreator = spy(new GithubProjectCreator(db.getDbClient(), githubApplicationClient, null, projectKeyGenerator, componentUpdater, githubProjectCreationParameters));
+    doReturn(Optional.of(devOpsProjectCreator)).when(devOpsProjectCreatorFactorySpy).getDevOpsProjectCreator(any(), eq(characteristics));
+    return devOpsProjectCreator;
   }
 
-  private void mockGithubInteractions(AlmSettingDto almSettingDto) {
-    GithubAppConfiguration githubAppConfiguration = mock(GithubAppConfiguration.class);
-    when(githubGlobalSettingsValidator.validate(almSettingDto)).thenReturn(githubAppConfiguration);
-    GithubAppInstallation githubAppInstallation = mock(GithubAppInstallation.class);
-    when(githubAppInstallation.installationId()).thenReturn("5435345");
-    when(githubApplicationClient.getWhitelistedGithubAppInstallations(any())).thenReturn(List.of(githubAppInstallation));
-    when(githubApplicationClient.createAppInstallationToken(any(), anyLong())).thenReturn(Optional.of(mock(AppInstallationToken.class)));
-    when(githubApplicationClient.createAppInstallationToken(any(), anyLong())).thenReturn(Optional.of(mock(AppInstallationToken.class)));
-    when(githubApplicationClient.getInstallationId(eq(githubAppConfiguration), any())).thenReturn(Optional.of(5435345L));
+  private void mockGithubRepository() {
     GithubApplicationClient.Repository repository = mock(GithubApplicationClient.Repository.class);
     when(repository.getDefaultBranch()).thenReturn("defaultBranch");
     when(repository.getFullName()).thenReturn("orga/repoName");
     when(repository.getName()).thenReturn("repoName");
     when(githubApplicationClient.getRepository(any(), any(), any())).thenReturn(Optional.of(repository));
-    when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn("projectKey");
   }
 
   @Test
index a403d13ee50783f392fa86577eb77aefff961137..6d83128baa5c62b9db4c65fd84ed316dba1b834c 100644 (file)
@@ -33,11 +33,13 @@ import org.sonar.db.alm.pat.AlmPatDto;
 import org.sonar.db.alm.setting.ALM;
 import org.sonar.db.alm.setting.AlmSettingDto;
 import org.sonar.db.component.BranchDto;
+import org.sonar.db.project.CreationMethod;
 import org.sonar.db.project.ProjectDto;
 import org.sonar.server.almintegration.ws.AlmIntegrationsWsAction;
 import org.sonar.server.almintegration.ws.ImportHelper;
+import org.sonar.server.almsettings.ws.DevOpsProjectCreator;
 import org.sonar.server.almsettings.ws.DevOpsProjectDescriptor;
-import org.sonar.server.almsettings.ws.GitHubDevOpsPlatformService;
+import org.sonar.server.almsettings.ws.GithubProjectCreatorFactory;
 import org.sonar.server.component.ComponentCreationData;
 import org.sonar.server.component.ComponentUpdater;
 import org.sonar.server.management.ManagedProjectService;
@@ -47,6 +49,8 @@ import org.sonar.server.user.UserSession;
 import org.sonarqube.ws.Projects;
 
 import static java.util.Objects.requireNonNull;
+import static org.sonar.db.project.CreationMethod.Category.ALM_IMPORT;
+import static org.sonar.db.project.CreationMethod.getCreationMethod;
 import static org.sonar.server.almintegration.ws.ImportHelper.PARAM_ALM_SETTING;
 import static org.sonar.server.almintegration.ws.ImportHelper.toCreateResponse;
 import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION;
@@ -70,13 +74,13 @@ public class ImportGithubProjectAction implements AlmIntegrationsWsAction {
 
   private final DefaultBranchNameResolver defaultBranchNameResolver;
 
-  private final GitHubDevOpsPlatformService gitHubDevOpsPlatformService;
+  private final GithubProjectCreatorFactory githubProjectCreatorFactory;
 
   @Inject
   public ImportGithubProjectAction(DbClient dbClient, ManagedProjectService managedProjectService, UserSession userSession,
     ComponentUpdater componentUpdater, ImportHelper importHelper,
     NewCodeDefinitionResolver newCodeDefinitionResolver,
-    DefaultBranchNameResolver defaultBranchNameResolver, GitHubDevOpsPlatformService gitHubDevOpsPlatformService) {
+    DefaultBranchNameResolver defaultBranchNameResolver, GithubProjectCreatorFactory githubProjectCreatorFactory) {
     this.dbClient = dbClient;
     this.managedProjectService = managedProjectService;
     this.userSession = userSession;
@@ -84,7 +88,7 @@ public class ImportGithubProjectAction implements AlmIntegrationsWsAction {
     this.importHelper = importHelper;
     this.newCodeDefinitionResolver = newCodeDefinitionResolver;
     this.defaultBranchNameResolver = defaultBranchNameResolver;
-    this.gitHubDevOpsPlatformService = gitHubDevOpsPlatformService;
+    this.githubProjectCreatorFactory = githubProjectCreatorFactory;
   }
 
   @Override
@@ -140,8 +144,10 @@ public class ImportGithubProjectAction implements AlmIntegrationsWsAction {
 
       String url = requireNonNull(almSettingDto.getUrl(), "DevOps Platform url cannot be null");
       DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, url, repositoryKey);
-      ComponentCreationData componentCreationData = gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, almSettingDto, accessToken,
-        devOpsProjectDescriptor);
+
+      Optional<DevOpsProjectCreator> devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, almSettingDto, accessToken, devOpsProjectDescriptor);
+      CreationMethod creationMethod = getCreationMethod(ALM_IMPORT, userSession.isAuthenticatedBrowserSession());
+      ComponentCreationData componentCreationData = devOpsProjectCreator.get().createProjectAndBindToDevOpsPlatform(dbSession, creationMethod, null);
 
       checkNewCodeDefinitionParam(newCodeDefinitionType, newCodeDefinitionValue);
 
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DelegatingDevOpsPlatformService.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DelegatingDevOpsPlatformService.java
deleted file mode 100644 (file)
index 608d8cf..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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.server.almsettings.ws;
-
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import javax.annotation.Priority;
-import org.apache.commons.lang.NotImplementedException;
-import org.sonar.alm.client.github.security.AccessToken;
-import org.sonar.api.server.ServerSide;
-import org.sonar.db.DbSession;
-import org.sonar.db.alm.setting.ALM;
-import org.sonar.db.alm.setting.AlmSettingDto;
-import org.sonar.server.component.ComponentCreationData;
-
-@ServerSide
-@Priority(1)
-public class DelegatingDevOpsPlatformService implements DevOpsPlatformService {
-
-  private final Set<DevOpsPlatformService> delegates;
-
-  public DelegatingDevOpsPlatformService(Set<DevOpsPlatformService> delegates) {
-    this.delegates = delegates;
-  }
-
-  @Override
-  public ALM getDevOpsPlatform() {
-    throw new NotImplementedException();
-  }
-
-  @Override
-  public Optional<DevOpsProjectDescriptor> getDevOpsProjectDescriptor(Map<String, String> characteristics) {
-    return delegates.stream()
-      .flatMap(delegate -> delegate.getDevOpsProjectDescriptor(characteristics).stream())
-      .findFirst();
-  }
-
-  @Override
-  public Optional<AlmSettingDto> getValidAlmSettingDto(DbSession dbSession, DevOpsProjectDescriptor devOpsProjectDescriptor) {
-    return findDelegate(devOpsProjectDescriptor.alm())
-      .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) {
-    return findDelegate(almSettingDto.getAlm())
-      .map(delegate -> delegate.createProjectAndBindToDevOpsPlatform(dbSession, projectKey, almSettingDto, devOpsProjectDescriptor))
-      .orElseThrow(() -> new IllegalStateException("Impossible to bind project to ALM platform " + almSettingDto.getAlm()));
-  }
-
-  @Override
-  public ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, AlmSettingDto almSettingDto, AccessToken accessToken,
-    DevOpsProjectDescriptor devOpsProjectDescriptor) {
-    return findDelegate(almSettingDto.getAlm())
-      .map(delegate -> delegate.createProjectAndBindToDevOpsPlatform(dbSession, almSettingDto, accessToken, devOpsProjectDescriptor))
-      .orElseThrow(() -> new IllegalStateException("Impossible to bind project to ALM platform " + almSettingDto.getAlm()));
-  }
-
-  private Optional<DevOpsPlatformService> findDelegate(ALM alm) {
-    return delegates.stream()
-      .filter(delegate -> delegate.getDevOpsPlatform().equals(alm))
-      .findFirst();
-  }
-
-}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DelegatingDevOpsProjectCreatorFactory.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DelegatingDevOpsProjectCreatorFactory.java
new file mode 100644 (file)
index 0000000..c05a2a0
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.server.almsettings.ws;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.Priority;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.DbSession;
+
+@ServerSide
+@Priority(1)
+public class DelegatingDevOpsProjectCreatorFactory implements DevOpsProjectCreatorFactory {
+
+  private final Set<DevOpsProjectCreatorFactory> delegates;
+
+  public DelegatingDevOpsProjectCreatorFactory(Set<DevOpsProjectCreatorFactory> delegates) {
+    this.delegates = delegates;
+  }
+
+  @Override
+  public Optional<DevOpsProjectCreator> getDevOpsProjectCreator(DbSession dbSession, Map<String, String> characteristics) {
+    return delegates.stream()
+      .flatMap(delegate -> delegate.getDevOpsProjectCreator(dbSession, characteristics).stream())
+      .findFirst();
+  }
+
+
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsPlatformService.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsPlatformService.java
deleted file mode 100644 (file)
index 6db06d8..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.server.almsettings.ws;
-
-import java.util.Map;
-import java.util.Optional;
-import org.sonar.alm.client.github.security.AccessToken;
-import org.sonar.db.DbSession;
-import org.sonar.db.alm.setting.ALM;
-import org.sonar.db.alm.setting.AlmSettingDto;
-import org.sonar.server.component.ComponentCreationData;
-
-public interface DevOpsPlatformService {
-
-  ALM getDevOpsPlatform();
-
-  Optional<DevOpsProjectDescriptor> getDevOpsProjectDescriptor(Map<String, String> characteristics);
-
-  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,
-    DevOpsProjectDescriptor devOpsProjectDescriptor);
-}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectCreator.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectCreator.java
new file mode 100644 (file)
index 0000000..093f783
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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.server.almsettings.ws;
+
+import javax.annotation.Nullable;
+import org.sonar.db.DbSession;
+import org.sonar.db.project.CreationMethod;
+import org.sonar.server.component.ComponentCreationData;
+
+public interface DevOpsProjectCreator {
+
+  boolean isScanAllowedUsingPermissionsFromDevopsPlatform();
+
+  ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, CreationMethod creationMethod, @Nullable String projectKey);
+
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectCreatorFactory.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectCreatorFactory.java
new file mode 100644 (file)
index 0000000..f18f2b5
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.almsettings.ws;
+
+import java.util.Map;
+import java.util.Optional;
+import org.sonar.db.DbSession;
+
+public interface DevOpsProjectCreatorFactory {
+
+  Optional<DevOpsProjectCreator> getDevOpsProjectCreator(DbSession dbSession, Map<String, String> characteristics);
+
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GitHubDevOpsPlatformService.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GitHubDevOpsPlatformService.java
deleted file mode 100644 (file)
index 9e69dae..0000000
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * 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.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;
-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;
-import org.sonar.server.component.ComponentUpdater;
-import org.sonar.server.component.NewComponent;
-import org.sonar.server.exceptions.NotFoundException;
-import org.sonar.server.project.ProjectDefaultVisibility;
-import org.sonar.server.user.UserSession;
-
-import static java.lang.String.format;
-import static java.util.Objects.requireNonNull;
-import static org.sonar.api.resources.Qualifiers.PROJECT;
-import static org.sonar.db.project.CreationMethod.Category.ALM_IMPORT;
-import static org.sonar.db.project.CreationMethod.SCANNER_API_DEVOPS_AUTO_CONFIG;
-import static org.sonar.db.project.CreationMethod.getCreationMethod;
-import static org.sonar.server.component.NewComponent.newComponentBuilder;
-
-@ServerSide
-public class GitHubDevOpsPlatformService implements DevOpsPlatformService {
-  private static final Logger LOG = LoggerFactory.getLogger(GitHubDevOpsPlatformService.class);
-
-  public static final String DEVOPS_PLATFORM_URL = "devOpsPlatformUrl";
-  public static final String DEVOPS_PLATFORM_PROJECT_IDENTIFIER = "devOpsPlatformProjectIdentifier";
-
-  private final DbClient dbClient;
-  private final GithubGlobalSettingsValidator githubGlobalSettingsValidator;
-  private final GithubApplicationClient githubApplicationClient;
-  private final ProjectDefaultVisibility projectDefaultVisibility;
-  private final ProjectKeyGenerator projectKeyGenerator;
-  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, GithubPermissionConverter githubPermissionConverter) {
-    this.dbClient = dbClient;
-    this.githubGlobalSettingsValidator = githubGlobalSettingsValidator;
-    this.githubApplicationClient = githubApplicationClient;
-    this.projectDefaultVisibility = projectDefaultVisibility;
-    this.projectKeyGenerator = projectKeyGenerator;
-    this.userSession = userSession;
-    this.componentUpdater = componentUpdater;
-    this.gitHubSettings = gitHubSettings;
-    this.githubPermissionConverter = githubPermissionConverter;
-  }
-
-  @Override
-  public ALM getDevOpsPlatform() {
-    return ALM.GITHUB;
-  }
-
-  @Override
-  public Optional<DevOpsProjectDescriptor> getDevOpsProjectDescriptor(Map<String, String> characteristics) {
-    String githubApiUrl = characteristics.get(DEVOPS_PLATFORM_URL);
-    String githubRepository = characteristics.get(DEVOPS_PLATFORM_PROJECT_IDENTIFIER);
-    if (githubApiUrl != null && githubRepository != null) {
-      return Optional.of(new DevOpsProjectDescriptor(ALM.GITHUB, githubApiUrl, githubRepository));
-    }
-    return Optional.empty();
-  }
-
-  @Override
-  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(devOpsProjectDescriptor, almSettingDto))
-      .findFirst();
-    if (configurationToUse.isPresent()) {
-      LOG.info("DevOps configuration {} auto-detected", configurationToUse.get().getKey());
-    } else {
-      LOG.info("Could not auto-detect a DevOps configuration for project {} (api url {})",
-        devOpsProjectDescriptor.projectIdentifier(), devOpsProjectDescriptor.url());
-    }
-    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, 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())));
-
-    return createProjectAndBindToDevOpsPlatform(dbSession, projectKey, almSettingDto, repository, SCANNER_API_DEVOPS_AUTO_CONFIG);
-  }
-
-  private Optional<Long> findInstallationIdToAccessRepo(AlmSettingDto almSettingDto, GithubAppConfiguration githubAppConfiguration, String repositoryKey) {
-    try {
-      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()));
-      return Optional.empty();
-    }
-  }
-
-  private Optional<GithubApplicationClient.Repository> findRepositoryOnGithub(String organizationAndRepository,
-    GithubAppConfiguration githubAppConfiguration, long installationId) {
-    AppInstallationToken accessToken = generateAppInstallationToken(githubAppConfiguration, installationId);
-    return githubApplicationClient.getRepository(githubAppConfiguration.getApiEndpoint(), accessToken, organizationAndRepository);
-  }
-
-  private AppInstallationToken generateAppInstallationToken(GithubAppConfiguration githubAppConfiguration, long installationId) {
-    return githubApplicationClient.createAppInstallationToken(githubAppConfiguration, installationId)
-      .orElseThrow(() -> new IllegalStateException(format("Error while generating token for GitHub Api Url %s (installation id: %s)",
-        githubAppConfiguration.getApiEndpoint(), installationId)));
-  }
-
-  @Override
-  public ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, AlmSettingDto almSettingDto, AccessToken accessToken,
-    DevOpsProjectDescriptor devOpsProjectDescriptor) {
-    String url = requireNonNull(almSettingDto.getUrl(), "DevOps Platform url cannot be null");
-    GithubApplicationClient.Repository repository = githubApplicationClient.getRepository(url, accessToken, devOpsProjectDescriptor.projectIdentifier())
-      .orElseThrow(() -> new NotFoundException(String.format("GitHub repository '%s' not found", devOpsProjectDescriptor)));
-
-    CreationMethod creationMethod = getCreationMethod(ALM_IMPORT, userSession.isAuthenticatedBrowserSession());
-    return createProjectAndBindToDevOpsPlatform(dbSession, null, almSettingDto, repository, creationMethod);
-  }
-
-  private ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, @Nullable String projectKey, AlmSettingDto almSettingDto,
-    GithubApplicationClient.Repository repository, CreationMethod creationMethod) {
-    ComponentCreationData componentCreationData = createProject(dbSession, projectKey, repository, creationMethod);
-    ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow();
-    createProjectAlmSettingDto(dbSession, repository, projectDto, almSettingDto);
-    return componentCreationData;
-  }
-
-  private ComponentCreationData createProject(DbSession dbSession, @Nullable String projectKey, GithubApplicationClient.Repository repository, CreationMethod creationMethod) {
-    boolean visibility = projectDefaultVisibility.get(dbSession).isPrivate();
-    NewComponent projectComponent = newComponentBuilder()
-      .setKey(Optional.ofNullable(projectKey).orElse(getUniqueProjectKey(repository)))
-      .setName(repository.getName())
-      .setPrivate(visibility)
-      .setQualifier(PROJECT)
-      .build();
-    ComponentCreationParameters componentCreationParameters = ComponentCreationParameters.builder()
-      .newComponent(projectComponent)
-      .userLogin(userSession.getLogin())
-      .userUuid(userSession.getUuid())
-      .mainBranchName(repository.getDefaultBranch())
-      .isManaged(gitHubSettings.isProvisioningEnabled())
-      .creationMethod(creationMethod)
-      .build();
-    return componentUpdater.createWithoutCommit(dbSession, componentCreationParameters);
-  }
-
-  private String getUniqueProjectKey(GithubApplicationClient.Repository repository) {
-    return projectKeyGenerator.generateUniqueProjectKey(repository.getFullName());
-  }
-
-  private void createProjectAlmSettingDto(DbSession dbSession, GithubApplicationClient.Repository repo, ProjectDto projectDto, AlmSettingDto almSettingDto) {
-    ProjectAlmSettingDto projectAlmSettingDto = new ProjectAlmSettingDto()
-      .setAlmSettingUuid(almSettingDto.getUuid())
-      .setAlmRepo(repo.getFullName())
-      .setAlmSlug(null)
-      .setProjectUuid(projectDto.getUuid())
-      .setSummaryCommentEnabled(true)
-      .setMonorepo(false);
-    dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, projectAlmSettingDto, almSettingDto.getKey(), projectDto.getName(), projectDto.getKey());
-  }
-
-}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreationParameters.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreationParameters.java
new file mode 100644 (file)
index 0000000..3f51f1b
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * 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.server.almsettings.ws;
+
+import org.sonar.alm.client.github.security.AccessToken;
+import org.sonar.db.alm.setting.AlmSettingDto;
+import org.sonar.server.user.UserSession;
+
+public record GithubProjectCreationParameters(DevOpsProjectDescriptor devOpsProjectDescriptor, AlmSettingDto almSettingDto, AccessToken appInstallationToken,
+                                              boolean projectsArePrivateByDefault, boolean isProvisioningEnabled, UserSession userSession) {
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreator.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreator.java
new file mode 100644 (file)
index 0000000..20a608c
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * 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.server.almsettings.ws;
+
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.Nullable;
+import org.sonar.alm.client.github.GithubApplicationClient;
+import org.sonar.alm.client.github.api.GsonRepositoryCollaborator;
+import org.sonar.alm.client.github.api.GsonRepositoryTeam;
+import org.sonar.alm.client.github.security.AccessToken;
+import org.sonar.api.web.UserRole;
+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.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;
+import org.sonar.server.component.ComponentUpdater;
+import org.sonar.server.component.NewComponent;
+import org.sonar.server.user.UserSession;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.toSet;
+import static org.sonar.api.resources.Qualifiers.PROJECT;
+import static org.sonar.server.component.NewComponent.newComponentBuilder;
+
+public class GithubProjectCreator implements DevOpsProjectCreator {
+
+  private final DbClient dbClient;
+  private final GithubApplicationClient githubApplicationClient;
+  private final GithubPermissionConverter githubPermissionConverter;
+  private final ProjectKeyGenerator projectKeyGenerator;
+  private final ComponentUpdater componentUpdater;
+  private final GithubProjectCreationParameters githubProjectCreationParameters;
+  private final DevOpsProjectDescriptor devOpsProjectDescriptor;
+  private final AccessToken appInstallationToken;
+  private final UserSession userSession;
+  private final AlmSettingDto almSettingDto;
+
+  public GithubProjectCreator(DbClient dbClient, GithubApplicationClient githubApplicationClient, GithubPermissionConverter githubPermissionConverter,
+    ProjectKeyGenerator projectKeyGenerator, ComponentUpdater componentUpdater, GithubProjectCreationParameters githubProjectCreationParameters) {
+
+    this.dbClient = dbClient;
+    this.githubApplicationClient = githubApplicationClient;
+    this.githubPermissionConverter = githubPermissionConverter;
+    this.projectKeyGenerator = projectKeyGenerator;
+    this.componentUpdater = componentUpdater;
+    this.githubProjectCreationParameters = githubProjectCreationParameters;
+    devOpsProjectDescriptor = githubProjectCreationParameters.devOpsProjectDescriptor();
+    appInstallationToken = githubProjectCreationParameters.appInstallationToken();
+    userSession = githubProjectCreationParameters.userSession();
+    almSettingDto = githubProjectCreationParameters.almSettingDto();
+  }
+
+  @Override
+  public boolean isScanAllowedUsingPermissionsFromDevopsPlatform() {
+    String[] orgaAndRepoTokenified = devOpsProjectDescriptor.projectIdentifier().split("/");
+    String organization = orgaAndRepoTokenified[0];
+    String repository = orgaAndRepoTokenified[1];
+
+    Set<GithubPermissionsMappingDto> permissionsMappingDtos = dbClient.githubPermissionsMappingDao().findAll(dbClient.openSession(false));
+
+    boolean userHasDirectAccessToRepo = doesUserHaveScanPermission(organization, repository, permissionsMappingDtos);
+    if (userHasDirectAccessToRepo) {
+      return true;
+    }
+    return doesUserBelongToAGroupWithScanPermission(organization, repository, permissionsMappingDtos);
+  }
+
+  private boolean doesUserHaveScanPermission(String organization, String repository, Set<GithubPermissionsMappingDto> permissionsMappingDtos) {
+    Set<GsonRepositoryCollaborator> repositoryCollaborators = githubApplicationClient.getRepositoryCollaborators(devOpsProjectDescriptor.url(), appInstallationToken, 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 organization, String repository,
+    Set<GithubPermissionsMappingDto> permissionsMappingDtos) {
+    Set<GsonRepositoryTeam> repositoryTeams = githubApplicationClient.getRepositoryTeams(devOpsProjectDescriptor.url(), appInstallationToken, 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(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, CreationMethod creationMethod, @Nullable String projectKey) {
+    String url = requireNonNull(almSettingDto.getUrl(), "DevOps Platform url cannot be null");
+    GithubApplicationClient.Repository repository = githubApplicationClient.getRepository(url, appInstallationToken, devOpsProjectDescriptor.projectIdentifier())
+      .orElseThrow(() -> new IllegalStateException(
+        String.format("Impossible to find the repository '%s' on GitHub, using the devops config %s", devOpsProjectDescriptor.projectIdentifier(), almSettingDto.getKey())));
+
+    return createProjectAndBindToDevOpsPlatform(dbSession, projectKey, almSettingDto, repository, creationMethod);
+  }
+
+  private ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, @Nullable String projectKey, AlmSettingDto almSettingDto,
+    GithubApplicationClient.Repository repository, CreationMethod creationMethod) {
+    ComponentCreationData componentCreationData = createProject(dbSession, projectKey, repository, creationMethod);
+    ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow();
+    createProjectAlmSettingDto(dbSession, repository, projectDto, almSettingDto);
+    return componentCreationData;
+  }
+
+  private ComponentCreationData createProject(DbSession dbSession, @Nullable String projectKey, GithubApplicationClient.Repository repository, CreationMethod creationMethod) {
+    NewComponent projectComponent = newComponentBuilder()
+      .setKey(Optional.ofNullable(projectKey).orElse(getUniqueProjectKey(repository)))
+      .setName(repository.getName())
+      .setPrivate(githubProjectCreationParameters.projectsArePrivateByDefault())
+      .setQualifier(PROJECT)
+      .build();
+    ComponentCreationParameters componentCreationParameters = ComponentCreationParameters.builder()
+      .newComponent(projectComponent)
+      .userLogin(userSession.getLogin())
+      .userUuid(userSession.getUuid())
+      .mainBranchName(repository.getDefaultBranch())
+      .isManaged(githubProjectCreationParameters.isProvisioningEnabled())
+      .creationMethod(creationMethod)
+      .build();
+    return componentUpdater.createWithoutCommit(dbSession, componentCreationParameters);
+  }
+
+  private String getUniqueProjectKey(GithubApplicationClient.Repository repository) {
+    return projectKeyGenerator.generateUniqueProjectKey(repository.getFullName());
+  }
+
+  private void createProjectAlmSettingDto(DbSession dbSession, GithubApplicationClient.Repository repo, ProjectDto projectDto, AlmSettingDto almSettingDto) {
+    ProjectAlmSettingDto projectAlmSettingDto = new ProjectAlmSettingDto()
+      .setAlmSettingUuid(almSettingDto.getUuid())
+      .setAlmRepo(repo.getFullName())
+      .setAlmSlug(null)
+      .setProjectUuid(projectDto.getUuid())
+      .setSummaryCommentEnabled(true)
+      .setMonorepo(false);
+    dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, projectAlmSettingDto, almSettingDto.getKey(), projectDto.getName(), projectDto.getKey());
+  }
+
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactory.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactory.java
new file mode 100644 (file)
index 0000000..59ca84e
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * 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.server.almsettings.ws;
+
+import java.util.Map;
+import java.util.Optional;
+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.config.GithubAppConfiguration;
+import org.sonar.alm.client.github.security.AccessToken;
+import org.sonar.api.server.ServerSide;
+import org.sonar.auth.github.GitHubSettings;
+import org.sonar.auth.github.GithubPermissionConverter;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.alm.setting.ALM;
+import org.sonar.db.alm.setting.AlmSettingDto;
+import org.sonar.server.almintegration.ws.ProjectKeyGenerator;
+import org.sonar.server.component.ComponentUpdater;
+import org.sonar.server.project.ProjectDefaultVisibility;
+import org.sonar.server.user.UserSession;
+
+import static java.lang.String.format;
+import static org.sonar.core.ce.CeTaskCharacteristics.DEVOPS_PLATFORM_PROJECT_IDENTIFIER;
+import static org.sonar.core.ce.CeTaskCharacteristics.DEVOPS_PLATFORM_URL;
+
+@ServerSide
+public class GithubProjectCreatorFactory implements DevOpsProjectCreatorFactory {
+  private static final Logger LOG = LoggerFactory.getLogger(GithubProjectCreatorFactory.class);
+
+  private final DbClient dbClient;
+  private final GithubGlobalSettingsValidator githubGlobalSettingsValidator;
+  private final GithubApplicationClient githubApplicationClient;
+  private final ProjectDefaultVisibility projectDefaultVisibility;
+  private final ProjectKeyGenerator projectKeyGenerator;
+  private final UserSession userSession;
+  private final ComponentUpdater componentUpdater;
+  private final GitHubSettings gitHubSettings;
+  private final GithubPermissionConverter githubPermissionConverter;
+
+  public GithubProjectCreatorFactory(DbClient dbClient, GithubGlobalSettingsValidator githubGlobalSettingsValidator,
+    GithubApplicationClient githubApplicationClient, ProjectDefaultVisibility projectDefaultVisibility, ProjectKeyGenerator projectKeyGenerator, UserSession userSession,
+    ComponentUpdater componentUpdater, GitHubSettings gitHubSettings, GithubPermissionConverter githubPermissionConverter) {
+    this.dbClient = dbClient;
+    this.githubGlobalSettingsValidator = githubGlobalSettingsValidator;
+    this.githubApplicationClient = githubApplicationClient;
+    this.projectDefaultVisibility = projectDefaultVisibility;
+    this.projectKeyGenerator = projectKeyGenerator;
+    this.userSession = userSession;
+    this.componentUpdater = componentUpdater;
+    this.gitHubSettings = gitHubSettings;
+    this.githubPermissionConverter = githubPermissionConverter;
+  }
+
+  @Override
+  public Optional<DevOpsProjectCreator> getDevOpsProjectCreator(DbSession dbSession, Map<String, String> characteristics) {
+    String githubApiUrl = characteristics.get(DEVOPS_PLATFORM_URL);
+    String githubRepository = characteristics.get(DEVOPS_PLATFORM_PROJECT_IDENTIFIER);
+    if (githubApiUrl == null || githubRepository == null) {
+      return Optional.empty();
+    }
+    DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, githubApiUrl, githubRepository);
+
+    Optional<DevOpsProjectCreator> githubProjectCreator = dbClient.almSettingDao().selectByAlm(dbSession, ALM.GITHUB).stream()
+      .filter(almSettingDto -> devOpsProjectDescriptor.url().equals(almSettingDto.getUrl()))
+      .map(almSettingDto -> findInstallationIdAndCreateDevOpsProjectCreator(dbSession, devOpsProjectDescriptor, almSettingDto))
+      .flatMap(Optional::stream)
+      .findFirst();
+
+    if (githubProjectCreator.isPresent()) {
+      return githubProjectCreator;
+    }
+
+    throw new IllegalStateException(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. Please check with a SonarQube administrator.",
+      devOpsProjectDescriptor.projectIdentifier(), devOpsProjectDescriptor.alm(), devOpsProjectDescriptor.url()));
+  }
+
+  private Optional<DevOpsProjectCreator> findInstallationIdAndCreateDevOpsProjectCreator(DbSession dbSession, DevOpsProjectDescriptor devOpsProjectDescriptor,
+    AlmSettingDto almSettingDto) {
+    GithubAppConfiguration githubAppConfiguration = githubGlobalSettingsValidator.validate(almSettingDto);
+    return findInstallationIdToAccessRepo(githubAppConfiguration, devOpsProjectDescriptor.projectIdentifier())
+      .map(installationId -> generateAppInstallationToken(githubAppConfiguration, installationId))
+      .map(appInstallationToken -> createGithubProjectCreator(dbSession, devOpsProjectDescriptor, almSettingDto, appInstallationToken));
+  }
+
+  private Optional<Long> findInstallationIdToAccessRepo(GithubAppConfiguration githubAppConfiguration, String repositoryKey) {
+    return githubApplicationClient.getInstallationId(githubAppConfiguration, repositoryKey);
+  }
+
+  private AppInstallationToken generateAppInstallationToken(GithubAppConfiguration githubAppConfiguration, long installationId) {
+    return githubApplicationClient.createAppInstallationToken(githubAppConfiguration, installationId)
+      .orElseThrow(() -> new IllegalStateException(format("Error while generating token for GitHub Api Url %s (installation id: %s)",
+        githubAppConfiguration.getApiEndpoint(), installationId)));
+  }
+
+  private GithubProjectCreator createGithubProjectCreator(DbSession dbSession, DevOpsProjectDescriptor devOpsProjectDescriptor, AlmSettingDto almSettingDto,
+    AppInstallationToken appInstallationToken) {
+    LOG.info("DevOps configuration {} auto-detected for project {}", almSettingDto.getKey(), devOpsProjectDescriptor.projectIdentifier());
+    GithubProjectCreationParameters githubProjectCreationParameters = new GithubProjectCreationParameters(devOpsProjectDescriptor,
+      almSettingDto, appInstallationToken, projectDefaultVisibility.get(dbSession).isPrivate(), gitHubSettings.isProvisioningEnabled(), userSession);
+    return new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, componentUpdater,
+      githubProjectCreationParameters);
+  }
+
+  public Optional<DevOpsProjectCreator> getDevOpsProjectCreator(DbSession dbSession, AlmSettingDto almSettingDto, AccessToken accessToken,
+    DevOpsProjectDescriptor devOpsProjectDescriptor) {
+    GithubProjectCreationParameters githubProjectCreationParameters = new GithubProjectCreationParameters(devOpsProjectDescriptor,
+      almSettingDto, accessToken, projectDefaultVisibility.get(dbSession).isPrivate(), gitHubSettings.isProvisioningEnabled(), userSession);
+    return Optional.of(
+      new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, componentUpdater, githubProjectCreationParameters)
+    );
+  }
+
+}
index 9807ed236daf87c5e3578bbdc9803f1a9c9baee0..4095ba195b2551cd61aafe6165ce5fce2fabb24b 100644 (file)
@@ -24,7 +24,6 @@ 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;
@@ -35,13 +34,12 @@ import org.sonar.ce.queue.CeTaskSubmit;
 import org.sonar.ce.task.CeTask;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
-import org.sonar.db.alm.setting.AlmSettingDto;
 import org.sonar.db.ce.CeTaskTypes;
 import org.sonar.db.component.BranchDto;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.permission.GlobalPermission;
-import org.sonar.server.almsettings.ws.DevOpsPlatformService;
-import org.sonar.server.almsettings.ws.DevOpsProjectDescriptor;
+import org.sonar.server.almsettings.ws.DevOpsProjectCreator;
+import org.sonar.server.almsettings.ws.DevOpsProjectCreatorFactory;
 import org.sonar.server.component.ComponentCreationData;
 import org.sonar.server.component.ComponentCreationParameters;
 import org.sonar.server.component.ComponentUpdater;
@@ -55,6 +53,7 @@ import org.sonar.server.user.UserSession;
 import static java.lang.String.format;
 import static org.apache.commons.lang.StringUtils.defaultIfBlank;
 import static org.sonar.db.project.CreationMethod.SCANNER_API;
+import static org.sonar.db.project.CreationMethod.SCANNER_API_DEVOPS_AUTO_CONFIG;
 import static org.sonar.server.component.NewComponent.newComponentBuilder;
 import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
 
@@ -68,12 +67,12 @@ public class ReportSubmitter {
   private final DbClient dbClient;
   private final BranchSupport branchSupport;
   private final ProjectDefaultVisibility projectDefaultVisibility;
-  private final DevOpsPlatformService devOpsPlatformService;
+  private final DevOpsProjectCreatorFactory devOpsProjectCreatorFactory;
   private final ManagedInstanceService managedInstanceService;
 
   public ReportSubmitter(CeQueue queue, UserSession userSession, ComponentUpdater componentUpdater,
     PermissionTemplateService permissionTemplateService, DbClient dbClient, BranchSupport branchSupport, ProjectDefaultVisibility projectDefaultVisibility,
-    DevOpsPlatformService devOpsPlatformService, ManagedInstanceService managedInstanceService) {
+    DevOpsProjectCreatorFactory devOpsProjectCreatorFactory, ManagedInstanceService managedInstanceService) {
     this.queue = queue;
     this.userSession = userSession;
     this.componentUpdater = componentUpdater;
@@ -81,7 +80,7 @@ public class ReportSubmitter {
     this.dbClient = dbClient;
     this.branchSupport = branchSupport;
     this.projectDefaultVisibility = projectDefaultVisibility;
-    this.devOpsPlatformService = devOpsPlatformService;
+    this.devOpsProjectCreatorFactory = devOpsProjectCreatorFactory;
     this.managedInstanceService = managedInstanceService;
   }
 
@@ -167,45 +166,25 @@ public class ReportSubmitter {
     DbSession dbSession, BranchSupport.ComponentKey componentKey) {
     userSession.checkPermission(GlobalPermission.PROVISION_PROJECTS);
 
-    DevOpsProjectDescriptor devOpsProjectDescriptor = devOpsPlatformService.getDevOpsProjectDescriptor(characteristics).orElse(null);
-    AlmSettingDto almSettingDto = getAlmSettingDto(dbSession, devOpsProjectDescriptor);
+    DevOpsProjectCreator devOpsProjectCreator = devOpsProjectCreatorFactory.getDevOpsProjectCreator(dbSession, characteristics).orElse(null);
 
-    throwIfNoValidDevOpsConfigurationFoundForDevOpsProject(devOpsProjectDescriptor, almSettingDto);
-    throwIfCurrentUserWouldNotHaveScanPermission(projectKey, dbSession, devOpsProjectDescriptor, almSettingDto);
+    throwIfCurrentUserWouldNotHaveScanPermission(projectKey, dbSession, devOpsProjectCreator);
 
-    if (almSettingDto != null) {
-      return devOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, projectKey, almSettingDto, devOpsProjectDescriptor);
+    if (devOpsProjectCreator != null) {
+      return devOpsProjectCreator.createProjectAndBindToDevOpsPlatform(dbSession, SCANNER_API_DEVOPS_AUTO_CONFIG, projectKey);
     }
     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;
-  }
-
-  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()));
-    }
-  }
-
-  private void throwIfCurrentUserWouldNotHaveScanPermission(String projectKey, DbSession dbSession, @Nullable DevOpsProjectDescriptor devOpsProjectDescriptor,
-    @Nullable AlmSettingDto almSettingDto) {
-    if (!wouldCurrentUserHaveScanPermission(projectKey, dbSession, devOpsProjectDescriptor, almSettingDto)) {
+  private void throwIfCurrentUserWouldNotHaveScanPermission(String projectKey, DbSession dbSession, @Nullable DevOpsProjectCreator devOpsProjectCreator) {
+    if (!wouldCurrentUserHaveScanPermission(projectKey, dbSession, devOpsProjectCreator)) {
       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);
+  private boolean wouldCurrentUserHaveScanPermission(String projectKey, DbSession dbSession, @Nullable DevOpsProjectCreator devOpsProjectCreator) {
+    if (managedInstanceService.isInstanceExternallyManaged() && devOpsProjectCreator != null) {
+      return devOpsProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform();
     }
     return permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(dbSession, userSession.getUuid(), projectKey);
   }
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/DelegatingDevOpsPlatformServiceTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/DelegatingDevOpsPlatformServiceTest.java
deleted file mode 100644 (file)
index cda5b38..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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.server.almsettings.ws;
-
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import org.apache.commons.lang.NotImplementedException;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.sonar.db.DbSession;
-import org.sonar.db.alm.setting.ALM;
-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;
-import static org.mockito.Mockito.when;
-
-public class DelegatingDevOpsPlatformServiceTest {
-
-  private static final DelegatingDevOpsPlatformService NO_DEVOPS_PLATFORMS = new DelegatingDevOpsPlatformService(emptySet());
-  private static final DelegatingDevOpsPlatformService MULTIPLE_DEVOPS_PLATFORMS = new DelegatingDevOpsPlatformService(
-    Set.of(mockGitHubDevOpsPlatformService(), mockAzureDevOpsPlatformService()));
-
-  @Mock
-  private DbSession dbSession;
-
-  @Test
-  public void getDevOpsPlatform_shouldThrow() {
-    assertThatThrownBy(NO_DEVOPS_PLATFORMS::getDevOpsPlatform)
-      .isInstanceOf(NotImplementedException.class);
-  }
-
-  @Test
-  public void getDevOpsProjectDescriptor_whenNoDelegates_shouldReturnOptionalEmpty() {
-    Optional<DevOpsProjectDescriptor> devOpsProjectDescriptor = NO_DEVOPS_PLATFORMS.getDevOpsProjectDescriptor(Map.of());
-
-    assertThat(devOpsProjectDescriptor).isEmpty();
-  }
-
-  @Test
-  public void getDevOpsProjectDescriptor_whenDelegates_shouldReturnDelegateResponse() {
-    Optional<DevOpsProjectDescriptor> devOpsProjectDescriptor = MULTIPLE_DEVOPS_PLATFORMS.getDevOpsProjectDescriptor(Map.of(
-      "githubUrl", "githubUrl"
-    ));
-
-    assertThat(devOpsProjectDescriptor)
-      .isPresent()
-      .get().usingRecursiveComparison().isEqualTo(new DevOpsProjectDescriptor(ALM.GITHUB, "githubUrl", "githubRepo"));
-  }
-
-  @Test
-  public void getValidAlmSettingDto_whenNoDelegates_shouldReturnOptionalEmpty() {
-    Optional<AlmSettingDto> almSettingDto = NO_DEVOPS_PLATFORMS.getValidAlmSettingDto(dbSession, mock(DevOpsProjectDescriptor.class));
-
-    assertThat(almSettingDto).isEmpty();
-  }
-
-  @Test
-  public void getValidAlmSettingDto_whenDelegates_shouldReturnDelegateResponse() {
-    Optional<AlmSettingDto> almSettingDto = MULTIPLE_DEVOPS_PLATFORMS.getValidAlmSettingDto(dbSession, new DevOpsProjectDescriptor(ALM.GITHUB, "githubUrl", "githubRepo"));
-
-    assertThat(almSettingDto)
-      .isPresent()
-      .get()
-      .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);
-    when(mockDevOpsPlatformService.getDevOpsProjectDescriptor(Map.of("githubUrl", "githubUrl")))
-      .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;
-  }
-
-  private static DevOpsPlatformService mockAzureDevOpsPlatformService() {
-    DevOpsPlatformService mockDevOpsPlatformService = mock();
-    when(mockDevOpsPlatformService.getDevOpsPlatform()).thenReturn(ALM.AZURE_DEVOPS);
-    when(mockDevOpsPlatformService.getDevOpsProjectDescriptor(Map.of("azureUrl", "azureUrl")))
-      .thenReturn(Optional.of(new DevOpsProjectDescriptor(ALM.AZURE_DEVOPS, "azureUrl", "azureProject")));
-    when(mockDevOpsPlatformService.getValidAlmSettingDto(any(), any()))
-      .thenReturn(Optional.of(new AlmSettingDto().setAlm(ALM.AZURE_DEVOPS)));
-    return mockDevOpsPlatformService;
-  }
-
-}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/DelegatingDevOpsProjectCreatorFactoryTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/DelegatingDevOpsProjectCreatorFactoryTest.java
new file mode 100644 (file)
index 0000000..f5f5036
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * 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.server.almsettings.ws;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import org.junit.Test;
+import org.sonar.db.DbSession;
+
+import static java.util.Collections.emptySet;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class DelegatingDevOpsProjectCreatorFactoryTest {
+
+  private static final DbSession DB_SESSION = mock();
+  private static final Map<String, String> CHARACTERISTICS = Map.of("toto", "tata");
+
+  @Test
+  public void getDevOpsProjectDescriptor_whenNoDelegates_shouldReturnEmptyOptional() {
+    DelegatingDevOpsProjectCreatorFactory noDelegates = new DelegatingDevOpsProjectCreatorFactory(emptySet());
+    Optional<DevOpsProjectCreator> devOpsProjectCreator = noDelegates.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS);
+    assertThat(devOpsProjectCreator).isEmpty();
+  }
+
+  @Test
+  public void getDevOpsProjectDescriptor_whenNoDelegatesReturningACreator_shouldReturnEmptyOptional() {
+    DelegatingDevOpsProjectCreatorFactory delegates = new DelegatingDevOpsProjectCreatorFactory(Set.of(mock(), mock()));
+    Optional<DevOpsProjectCreator> devOpsProjectCreator = delegates.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS);
+
+    assertThat(devOpsProjectCreator).isEmpty();
+  }
+
+  @Test
+  public void getDevOpsProjectDescriptor_whenOneDelegatesReturningACreator_shouldDelegate() {
+    DevOpsProjectCreatorFactory successfulDelegate = mock();
+    DevOpsProjectCreator devOpsProjectCreator = mock();
+    when(successfulDelegate.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS)).thenReturn(Optional.of(devOpsProjectCreator));
+    DelegatingDevOpsProjectCreatorFactory delegates = new DelegatingDevOpsProjectCreatorFactory(Set.of(mock(), successfulDelegate));
+
+    assertThat(delegates.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS)).contains(devOpsProjectCreator);
+  }
+
+}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GitHubDevOpsPlatformServiceTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GitHubDevOpsPlatformServiceTest.java
deleted file mode 100644 (file)
index 0820c28..0000000
+++ /dev/null
@@ -1,546 +0,0 @@
-/*
- * 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.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;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-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;
-import org.sonar.db.alm.setting.AlmSettingDto;
-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;
-import org.sonar.server.component.ComponentUpdater;
-import org.sonar.server.component.NewComponent;
-import org.sonar.server.project.ProjectDefaultVisibility;
-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;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.sonar.server.almsettings.ws.GitHubDevOpsPlatformService.DEVOPS_PLATFORM_PROJECT_IDENTIFIER;
-import static org.sonar.server.almsettings.ws.GitHubDevOpsPlatformService.DEVOPS_PLATFORM_URL;
-
-@RunWith(MockitoJUnitRunner.class)
-public class GitHubDevOpsPlatformServiceTest {
-  @Rule
-  public LogTester logTester = new LogTester().setLevel(LoggerLevel.WARN);
-
-  private static final DevOpsProjectDescriptor GITHUB_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITHUB, "url", "repo");
-  private static final long APP_INSTALLATION_ID = 534534534543L;
-  private static final String USER_LOGIN = "user-login-1";
-  private static final String USER_UUID = "user-uuid-1";
-  private static final String PROJECT_KEY = "projectKey";
-  private static final String PROJECT_NAME = "projectName";
-  private static final String MAIN_BRANCH_NAME = "defaultBranch";
-  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;
-  @Mock
-  private GithubGlobalSettingsValidator githubGlobalSettingsValidator;
-  @Mock
-  private GithubApplicationClient githubApplicationClient;
-
-  @Mock
-  private ComponentUpdater componentUpdater;
-
-  @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-  private DbClient dbClient;
-  @Mock
-  private UserSession userSession;
-
-  @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-  private ProjectDefaultVisibility projectDefaultVisibility;
-
-  @Mock
-  private ProjectKeyGenerator projectKeyGenerator;
-
-  @Mock
-  private GitHubSettings gitHubSettings;
-
-  @Mock
-  private GithubPermissionConverter githubPermissionConverter;
-
-  @InjectMocks
-  private GitHubDevOpsPlatformService gitHubDevOpsPlatformService;
-
-  @Captor
-  ArgumentCaptor<ComponentCreationParameters> componentCreationParametersCaptor;
-  @Captor
-  ArgumentCaptor<ProjectAlmSettingDto> projectAlmSettingDtoCaptor;
-
-  @Before
-  public void setup() {
-    when(userSession.getLogin()).thenReturn(USER_LOGIN);
-    when(userSession.getUuid()).thenReturn(USER_UUID);
-  }
-
-  @Test
-  public void getDevOpsPlatform_shouldReturnGitHub() {
-    assertThat(gitHubDevOpsPlatformService.getDevOpsPlatform())
-      .isEqualTo(ALM.GITHUB);
-  }
-
-  @Test
-  public void getDevOpsProjectDescriptor_whenNoCharacteristics_shouldReturnEmpty() {
-    Optional<DevOpsProjectDescriptor> devOpsProjectDescriptor = gitHubDevOpsPlatformService.getDevOpsProjectDescriptor(Map.of());
-
-    assertThat(devOpsProjectDescriptor).isEmpty();
-  }
-
-  @Test
-  public void getDevOpsProjectDescriptor_whenValidCharacteristics_shouldReturn() {
-    Optional<DevOpsProjectDescriptor> devOpsProjectDescriptor = gitHubDevOpsPlatformService.getDevOpsProjectDescriptor(Map.of(
-      DEVOPS_PLATFORM_URL, GITHUB_PROJECT_DESCRIPTOR.url(),
-      DEVOPS_PLATFORM_PROJECT_IDENTIFIER, GITHUB_PROJECT_DESCRIPTOR.projectIdentifier()
-    ));
-
-    assertThat(devOpsProjectDescriptor)
-      .isPresent()
-      .get().usingRecursiveComparison().isEqualTo(GITHUB_PROJECT_DESCRIPTOR);
-  }
-
-  @Test
-  public void getValidAlmSettingDto_whenNoAlmSetting_shouldReturnEmpty() {
-    Optional<AlmSettingDto> almSettingDto = gitHubDevOpsPlatformService.getValidAlmSettingDto(dbSession, GITHUB_PROJECT_DESCRIPTOR);
-
-    assertThat(almSettingDto).isEmpty();
-  }
-
-  @Test
-  public void getValidAlmSettingDto_whenMultipleAlmSetting_shouldReturnTheRightOne() {
-    AlmSettingDto mockGitHubAlmSettingDtoNoAccess = mockGitHubAlmSettingDto(false);
-    AlmSettingDto mockGitHubAlmSettingDtoAccess = mockGitHubAlmSettingDto(true);
-    when(dbClient.almSettingDao().selectByAlm(dbSession, ALM.GITHUB)).thenReturn(List.of(mockGitHubAlmSettingDtoNoAccess, mockGitHubAlmSettingDtoAccess));
-
-    Optional<AlmSettingDto> almSettingDto = gitHubDevOpsPlatformService.getValidAlmSettingDto(dbSession, GITHUB_PROJECT_DESCRIPTOR);
-
-    assertThat(almSettingDto)
-      .isPresent()
-      .get().isEqualTo(mockGitHubAlmSettingDtoAccess);
-  }
-
-  private AlmSettingDto mockGitHubAlmSettingDto(boolean repoAccess) {
-    AlmSettingDto mockAlmSettingDto = mock();
-    when(mockAlmSettingDto.getUrl()).thenReturn(GITHUB_PROJECT_DESCRIPTOR.url());
-    GithubAppConfiguration mockGithubAppConfiguration = mock(GithubAppConfiguration.class);
-    when(githubGlobalSettingsValidator.validate(mockAlmSettingDto)).thenReturn(mockGithubAppConfiguration);
-    when(githubApplicationClient.getInstallationId(eq(mockGithubAppConfiguration), any())).thenReturn(repoAccess ? Optional.of(1L) : Optional.empty());
-    return mockAlmSettingDto;
-  }
-
-  @Test
-  public void createProjectAndBindToDevOpsPlatform_whenRepoNotFound_throws() {
-
-    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, 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
-    AlmSettingDto almSettingDto = mockAlmSettingDto();
-    mockExistingGitHubRepository(almSettingDto);
-
-    ComponentCreationData componentCreationData = mockProjectCreation();
-    ProjectAlmSettingDao projectAlmSettingDao = mock();
-    when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao);
-
-    // when
-    ComponentCreationData actualComponentCreationData = gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, PROJECT_KEY, almSettingDto,
-      DEV_OPS_PROJECT_DESCRIPTOR);
-
-    // then
-    assertThat(actualComponentCreationData).isEqualTo(componentCreationData);
-
-    ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue();
-    assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters);
-    assertThat(componentCreationParameters.isManaged()).isFalse();
-    assertThat(componentCreationParameters.newComponent().isPrivate()).isFalse();
-
-    verify(projectAlmSettingDao).insertOrUpdate(eq(dbSession), projectAlmSettingDtoCaptor.capture(), eq("devops-platform-config-1"), eq(PROJECT_NAME), eq(PROJECT_KEY));
-    ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue();
-    assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto);
-
-    assertThat(logTester.getLogs()).isEmpty();
-  }
-
-  @Test
-  public void createProjectAndBindToDevOpsPlatform_whenRepoFoundOnGitHubAndAutoProvisioningOn_successfullyCreatesProject() {
-    // given
-    when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE);
-    when(gitHubSettings.isProvisioningEnabled()).thenReturn(true);
-
-    AlmSettingDto almSettingDto = mockAlmSettingDto();
-    mockExistingGitHubRepository(almSettingDto);
-
-    ComponentCreationData componentCreationData = mockProjectCreation();
-    ProjectAlmSettingDao projectAlmSettingDao = mock();
-    when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao);
-
-    // when
-    ComponentCreationData actualComponentCreationData = gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, PROJECT_KEY, almSettingDto,
-      DEV_OPS_PROJECT_DESCRIPTOR);
-
-    // then
-    assertThat(actualComponentCreationData).isEqualTo(componentCreationData);
-
-    ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue();
-    assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters);
-    assertThat(componentCreationParameters.isManaged()).isTrue();
-    assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue();
-
-    verify(projectAlmSettingDao).insertOrUpdate(eq(dbSession), projectAlmSettingDtoCaptor.capture(), eq("devops-platform-config-1"), eq(PROJECT_NAME), eq(PROJECT_KEY));
-    ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue();
-    assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto);
-
-    assertThat(logTester.getLogs()).isEmpty();
-  }
-
-  @Test
-  public void createProjectAndBindToDevOpsPlatform_whenWrongToken_throws() {
-    AlmSettingDto almSettingDto = mockAlmSettingDto();
-    mockExistingGitHubRepository(almSettingDto);
-
-    when(githubApplicationClient.createAppInstallationToken(any(), anyLong())).thenReturn(Optional.empty());
-
-    assertThatIllegalStateException().isThrownBy(
-        () -> 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));
-    AppInstallationToken appInstallationToken = mockAppInstallationToken(githubAppConfiguration, APP_INSTALLATION_ID);
-    mockGitHubRepository(appInstallationToken);
-  }
-
-  private GithubAppConfiguration mockGitHubAppConfiguration(AlmSettingDto almSettingDto) {
-    GithubAppConfiguration githubAppConfiguration = mock();
-    when(githubGlobalSettingsValidator.validate(almSettingDto)).thenReturn(githubAppConfiguration);
-    when(githubAppConfiguration.getApiEndpoint()).thenReturn(GITHUB_API_URL);
-    return githubAppConfiguration;
-  }
-
-  private void mockGitHubRepository(AppInstallationToken appInstallationToken) {
-    GithubApplicationClient.Repository repository = mock();
-    when(repository.getDefaultBranch()).thenReturn(MAIN_BRANCH_NAME);
-    when(repository.getName()).thenReturn(PROJECT_NAME);
-    when(repository.getFullName()).thenReturn(GITHUB_REPO_FULL_NAME);
-    when(githubApplicationClient.getRepository(GITHUB_API_URL, appInstallationToken, GITHUB_REPO_FULL_NAME)).thenReturn(Optional.of(repository));
-    when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn("generated_" + PROJECT_KEY);
-  }
-
-  private AppInstallationToken mockAppInstallationToken(GithubAppConfiguration githubAppConfiguration, long appInstallationId) {
-    AppInstallationToken appInstallationToken = mock();
-    when(githubApplicationClient.createAppInstallationToken(githubAppConfiguration, appInstallationId)).thenReturn(Optional.of(appInstallationToken));
-    return appInstallationToken;
-  }
-
-  private static AlmSettingDto mockAlmSettingDto() {
-    AlmSettingDto almSettingDto = mock();
-    when(almSettingDto.getUuid()).thenReturn("almsetting-uuid-1");
-    when(almSettingDto.getKey()).thenReturn("devops-platform-config-1");
-    return almSettingDto;
-  }
-
-  private ComponentCreationData mockProjectCreation() {
-    ComponentCreationData componentCreationData = mock();
-    mockProjectDto(componentCreationData);
-    when(componentUpdater.createWithoutCommit(eq(dbSession), componentCreationParametersCaptor.capture())).thenReturn(componentCreationData);
-    return componentCreationData;
-  }
-
-  private static ProjectDto mockProjectDto(ComponentCreationData componentCreationData) {
-    ProjectDto projectDto = mock();
-    when(projectDto.getName()).thenReturn(PROJECT_NAME);
-    when(projectDto.getKey()).thenReturn(PROJECT_KEY);
-    when(projectDto.getUuid()).thenReturn("project-uuid-1");
-    when(componentCreationData.projectDto()).thenReturn(projectDto);
-    return projectDto;
-  }
-
-  private static void assertComponentCreationParametersContainsCorrectInformation(ComponentCreationParameters componentCreationParameters) {
-    assertThat(componentCreationParameters.creationMethod()).isEqualTo(CreationMethod.SCANNER_API_DEVOPS_AUTO_CONFIG);
-    assertThat(componentCreationParameters.mainBranchName()).isEqualTo(MAIN_BRANCH_NAME);
-    assertThat(componentCreationParameters.userLogin()).isEqualTo(USER_LOGIN);
-    assertThat(componentCreationParameters.userUuid()).isEqualTo(USER_UUID);
-
-    NewComponent newComponent = componentCreationParameters.newComponent();
-    assertThat(newComponent.isProject()).isTrue();
-    assertThat(newComponent.qualifier()).isEqualTo(Qualifiers.PROJECT);
-    assertThat(newComponent.key()).isEqualTo(PROJECT_KEY);
-    assertThat(newComponent.name()).isEqualTo(PROJECT_NAME);
-  }
-
-  private static void assertAlmSettingsDtoContainsCorrectInformation(AlmSettingDto almSettingDto, ProjectDto projectDto, ProjectAlmSettingDto projectAlmSettingDto) {
-    assertThat(projectAlmSettingDto.getAlmRepo()).isEqualTo(GITHUB_REPO_FULL_NAME);
-    assertThat(projectAlmSettingDto.getAlmSlug()).isNull();
-    assertThat(projectAlmSettingDto.getAlmSettingUuid()).isEqualTo(almSettingDto.getUuid());
-    assertThat(projectAlmSettingDto.getProjectUuid()).isEqualTo(projectDto.getUuid());
-    assertThat(projectAlmSettingDto.getMonorepo()).isFalse();
-    assertThat(projectAlmSettingDto.getSummaryCommentEnabled()).isTrue();
-  }
-}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactoryTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactoryTest.java
new file mode 100644 (file)
index 0000000..acb20e3
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * 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.server.almsettings.ws;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+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.auth.github.GitHubSettings;
+import org.sonar.auth.github.GithubPermissionConverter;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.alm.setting.ALM;
+import org.sonar.db.alm.setting.AlmSettingDto;
+import org.sonar.server.almintegration.ws.ProjectKeyGenerator;
+import org.sonar.server.component.ComponentUpdater;
+import org.sonar.server.project.ProjectDefaultVisibility;
+import org.sonar.server.user.UserSession;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.core.ce.CeTaskCharacteristics.DEVOPS_PLATFORM_PROJECT_IDENTIFIER;
+import static org.sonar.core.ce.CeTaskCharacteristics.DEVOPS_PLATFORM_URL;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GithubProjectCreatorFactoryTest {
+  private static final String EXCEPTION_MESSAGE =
+    "The project orgname/projectName could not be created. It was auto-detected as a GITHUB project and no valid DevOps platform configuration were found to access https://api.toto.com."
+    + " Please check with a SonarQube administrator.";
+
+  private static final String PROJECT_NAME = "projectName";
+  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 GITHUB_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITHUB, GITHUB_API_URL, GITHUB_REPO_FULL_NAME);
+  private static final Map<String, String> VALID_GITHUB_PROJECT_COORDINATES = Map.of(
+    DEVOPS_PLATFORM_URL, GITHUB_PROJECT_DESCRIPTOR.url(),
+    DEVOPS_PLATFORM_PROJECT_IDENTIFIER, GITHUB_PROJECT_DESCRIPTOR.projectIdentifier()
+  );
+  private static final long APP_INSTALLATION_ID = 534534534543L;
+
+  @Mock
+  private DbSession dbSession;
+  @Mock
+  private GithubGlobalSettingsValidator githubGlobalSettingsValidator;
+  @Mock
+  private GithubApplicationClient githubApplicationClient;
+
+  @Mock
+  private ComponentUpdater componentUpdater;
+
+  @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+  private DbClient dbClient;
+  @Mock
+  private UserSession userSession;
+
+  @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+  private ProjectDefaultVisibility projectDefaultVisibility;
+
+  @Mock
+  private ProjectKeyGenerator projectKeyGenerator;
+
+  @Mock
+  private GitHubSettings gitHubSettings;
+
+  @Mock
+  private GithubPermissionConverter githubPermissionConverter;
+
+  @Mock
+  private AppInstallationToken appInstallationToken;
+
+  @InjectMocks
+  private GithubProjectCreatorFactory githubProjectCreatorFactory;
+
+  @Test
+  public void getDevOpsProjectCreator_whenNoCharacteristics_shouldReturnEmpty() {
+    Optional<DevOpsProjectCreator> devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, Map.of());
+
+    assertThat(devOpsProjectCreator).isEmpty();
+  }
+
+  @Test
+  public void getDevOpsProjectCreator_whenValidCharacteristicsButNoAlmSettingDao_shouldThrow() {
+    assertThatIllegalStateException().isThrownBy(() -> githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES))
+      .withMessage(EXCEPTION_MESSAGE);
+  }
+
+  @Test
+  public void getDevOpsProjectCreator_whenValidCharacteristicsButInvalidAlmSettingDto_shouldThrow() {
+    AlmSettingDto almSettingDto = mockAlmSettingDto(true);
+    IllegalArgumentException error = new IllegalArgumentException("error happened");
+    when(githubGlobalSettingsValidator.validate(almSettingDto)).thenThrow(error);
+
+    assertThatIllegalArgumentException().isThrownBy(() -> githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES))
+      .isSameAs(error);
+  }
+
+  @Test
+  public void getDevOpsProjectCreator_whenAppHasNoAccessToRepo_shouldThrow() {
+    mockAlmSettingDto(true);
+    when(githubApplicationClient.getInstallationId(any(), eq(GITHUB_REPO_FULL_NAME))).thenReturn(Optional.empty());
+
+    assertThatIllegalStateException().isThrownBy(() -> githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES))
+      .withMessage(EXCEPTION_MESSAGE);
+  }
+
+  @Test
+  public void getDevOpsProjectCreator_whenNotPossibleToGenerateToken_shouldThrow() {
+    AlmSettingDto almSettingDto = mockAlmSettingDto(true);
+    when(githubApplicationClient.getInstallationId(any(), eq(GITHUB_REPO_FULL_NAME))).thenReturn(Optional.of(APP_INSTALLATION_ID));
+    when(githubGlobalSettingsValidator.validate(almSettingDto)).thenReturn(mock());
+    when(githubApplicationClient.createAppInstallationToken(any(), eq(APP_INSTALLATION_ID))).thenReturn(Optional.empty());
+
+    assertThatIllegalStateException().isThrownBy(() -> githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES))
+      .withMessage("Error while generating token for GitHub Api Url null (installation id: 1)");
+  }
+
+  @Test
+  public void getDevOpsProjectCreator_whenOneValidAlmSetting_shouldInstantiateDevOpsProjectCreator() {
+    AlmSettingDto almSettingDto = mockAlmSettingDto(true);
+    mockSuccesfullGithubInteraction();
+
+    DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES).orElseThrow();
+
+    GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(almSettingDto, false, false);
+    assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedGithubProjectCreator);
+  }
+
+  @Test
+  public void getDevOpsProjectCreator_whenOneValidAlmSettingAndPublicByDefaultAndAutoProviisoningEnabled_shouldInstantiateDevOpsProjectCreator() {
+    when(projectDefaultVisibility.get(any()).isPrivate()).thenReturn(true);
+    when(gitHubSettings.isProvisioningEnabled()).thenReturn(true);
+
+    AlmSettingDto almSettingDto = mockAlmSettingDto(true);
+    mockSuccesfullGithubInteraction();
+
+    DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES).orElseThrow();
+
+    GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(almSettingDto, true, true);
+    assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedGithubProjectCreator);
+  }
+
+  @Test
+  public void getDevOpsProjectCreator_whenOneMatchingAndOneNotMatchingAlmSetting_shouldInstantiateDevOpsProjectCreator() {
+    AlmSettingDto matchingAlmSettingDto = mockAlmSettingDto(true);
+    AlmSettingDto notMatchingAlmSettingDto = mockAlmSettingDto(false);
+    when(dbClient.almSettingDao().selectByAlm(dbSession, ALM.GITHUB)).thenReturn(List.of(notMatchingAlmSettingDto, matchingAlmSettingDto));
+
+    mockSuccesfullGithubInteraction();
+
+    DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES).orElseThrow();
+
+    GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(matchingAlmSettingDto, false, false);
+    assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedGithubProjectCreator);
+  }
+
+  @Test
+  public void getDevOpsProjectCreatorFromImport_shouldInstantiateDevOpsProjectCreator() {
+    AlmSettingDto mockAlmSettingDto = mockAlmSettingDto(true);
+
+    mockSuccesfullGithubInteraction();
+
+    DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, mockAlmSettingDto, appInstallationToken, GITHUB_PROJECT_DESCRIPTOR)
+      .orElseThrow();
+
+    GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(mockAlmSettingDto, false, false);
+    assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedGithubProjectCreator);
+  }
+
+  private void mockSuccesfullGithubInteraction() {
+    when(githubApplicationClient.getInstallationId(any(), eq(GITHUB_REPO_FULL_NAME))).thenReturn(Optional.of(APP_INSTALLATION_ID));
+    when(githubApplicationClient.createAppInstallationToken(any(), eq(APP_INSTALLATION_ID))).thenReturn(Optional.of(appInstallationToken));
+  }
+
+  private GithubProjectCreator getExpectedGithubProjectCreator(AlmSettingDto almSettingDto, boolean projectsArePrivateByDefault, boolean isInstanceManaged) {
+    DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, almSettingDto.getUrl(), GITHUB_REPO_FULL_NAME);
+    GithubProjectCreationParameters githubProjectCreationParameters = new GithubProjectCreationParameters(devOpsProjectDescriptor,
+      almSettingDto, appInstallationToken, projectsArePrivateByDefault, isInstanceManaged, userSession);
+    return new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, componentUpdater, githubProjectCreationParameters);
+  }
+
+
+  private AlmSettingDto mockAlmSettingDto(boolean repoAccess) {
+    AlmSettingDto almSettingDto = mock();
+    when(almSettingDto.getUrl()).thenReturn(repoAccess ? GITHUB_PROJECT_DESCRIPTOR.url() : "anotherUrl");
+
+    when(dbClient.almSettingDao().selectByAlm(dbSession, ALM.GITHUB)).thenReturn(List.of(almSettingDto));
+    return almSettingDto;
+  }
+
+}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorTest.java
new file mode 100644 (file)
index 0000000..7cd0fce
--- /dev/null
@@ -0,0 +1,369 @@
+/*
+ * 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.server.almsettings.ws;
+
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.Set;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.sonar.alm.client.github.GithubApplicationClient;
+import org.sonar.alm.client.github.api.GsonRepositoryCollaborator;
+import org.sonar.alm.client.github.api.GsonRepositoryTeam;
+import org.sonar.alm.client.github.security.AccessToken;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.web.UserRole;
+import org.sonar.auth.github.GithubPermissionConverter;
+import org.sonar.auth.github.GsonRepositoryPermissions;
+import org.sonar.db.DbClient;
+import org.sonar.db.alm.setting.ALM;
+import org.sonar.db.alm.setting.AlmSettingDto;
+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;
+import org.sonar.server.component.ComponentUpdater;
+import org.sonar.server.component.NewComponent;
+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;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.sonar.db.project.CreationMethod.ALM_IMPORT_API;
+import static org.sonar.db.project.CreationMethod.SCANNER_API_DEVOPS_AUTO_CONFIG;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GithubProjectCreatorTest {
+
+  private static final String ORGANIZATION_NAME = "orga2";
+  private static final String REPOSITORY_NAME = "repo1";
+
+  private static final String MAIN_BRANCH_NAME = "defaultBranch";
+  private static final DevOpsProjectDescriptor DEVOPS_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITHUB, "http://api.com", ORGANIZATION_NAME + "/" + REPOSITORY_NAME);
+  private static final String ALM_SETTING_KEY = "github_config_1";
+  private static final String USER_LOGIN = "userLogin";
+  private static final String USER_UUID = "userUuid";
+  @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+  private DbClient dbClient;
+  @Mock
+  private GithubApplicationClient githubApplicationClient;
+  @Mock
+  private GithubPermissionConverter githubPermissionConverter;
+  @Mock
+  private ProjectKeyGenerator projectKeyGenerator;
+  @Mock
+  private ComponentUpdater componentUpdater;
+  @Mock
+  private GithubProjectCreationParameters githubProjectCreationParameters;
+  @Mock
+  private AccessToken appInstallationToken;
+  @Mock
+  private UserSession userSession;
+  @Mock
+  private AlmSettingDto almSettingDto;
+
+  private GithubProjectCreator githubProjectCreator;
+
+  @Captor
+  ArgumentCaptor<ComponentCreationParameters> componentCreationParametersCaptor;
+  @Captor
+  ArgumentCaptor<ProjectAlmSettingDto> projectAlmSettingDtoCaptor;
+
+  @Before
+  public void setup() {
+    when(userSession.getLogin()).thenReturn(USER_LOGIN);
+    when(userSession.getUuid()).thenReturn(USER_UUID);
+
+    when(almSettingDto.getUrl()).thenReturn(DEVOPS_PROJECT_DESCRIPTOR.url());
+    when(almSettingDto.getKey()).thenReturn(ALM_SETTING_KEY);
+
+    when(githubProjectCreationParameters.devOpsProjectDescriptor()).thenReturn(DEVOPS_PROJECT_DESCRIPTOR);
+    when(githubProjectCreationParameters.userSession()).thenReturn(userSession);
+    when(githubProjectCreationParameters.appInstallationToken()).thenReturn(appInstallationToken);
+    when(githubProjectCreationParameters.almSettingDto()).thenReturn(almSettingDto);
+
+    githubProjectCreator = new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, componentUpdater,
+      githubProjectCreationParameters);
+
+   /* when(githubProjectCreationParameters.almSettingDto()).thenReturn();
+    when(githubProjectCreationParameters.almSettingDto()).thenReturn();
+    when(githubProjectCreationParameters.almSettingDto()).thenReturn();
+    when(githubProjectCreationParameters.almSettingDto()).thenReturn();*/
+  }
+
+  @Test
+  public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenUserIsNotAGitHubUser_returnsFalse() {
+    assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isFalse();
+  }
+
+  @Test
+  public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenCollaboratorHasDirectAccessButNoScanPermissions_returnsFalse() {
+    GsonRepositoryCollaborator collaborator1 = mockCollaborator("collaborator1", 1, "role1", "read", "admin");
+    mockGithubCollaboratorsFromApi(collaborator1);
+    bindSessionToCollaborator(collaborator1);
+
+    assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isFalse();
+  }
+
+  @Test
+  public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenCollaboratorHasDirectAccess_returnsTrue() {
+    GsonRepositoryCollaborator collaborator1 = mockCollaborator("collaborator1", 1, "role1", "read", "admin");
+    GsonRepositoryCollaborator collaborator2 = mockCollaborator("collaborator2", 2, "role2", "read", "scan");
+    mockGithubCollaboratorsFromApi(collaborator1, collaborator2);
+    bindSessionToCollaborator(collaborator2);
+
+    assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isTrue();
+  }
+
+  @Test
+  public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenAccessViaTeamButNoScanPermissions_returnsFalse() {
+    GsonRepositoryTeam team2 = mockGithubTeam("team2", 2, "role2", "another_perm", UserRole.ADMIN);
+    mockTeamsFromApi(team2);
+    bindGroupsToUser(team2.name());
+
+    assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isFalse();
+  }
+
+  @Test
+  public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenAccessViaTeam_returnsTrue() {
+    GsonRepositoryTeam team1 = mockGithubTeam("team1", 1, "role1", "read", "another_perm");
+    GsonRepositoryTeam team2 = mockGithubTeam("team2", 2, "role2", "another_perm", UserRole.SCAN);
+    mockTeamsFromApi(team1, team2);
+    bindGroupsToUser(team1.name(), team2.name());
+
+    assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isTrue();
+  }
+
+  @Test
+  public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenAccessViaTeamButUserNotInTeam_returnsFalse() {
+    GsonRepositoryTeam team1 = mockGithubTeam("team1", 1, "role1", "read", "another_perm");
+    GsonRepositoryTeam team2 = mockGithubTeam("team2", 2, "role2", "another_perm", UserRole.SCAN);
+    mockTeamsFromApi(team1, team2);
+    bindGroupsToUser(team1.name());
+
+    assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isFalse();
+  }
+
+  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 GsonRepositoryCollaborator mockCollaborator(String collaboratorLogin, int id, String role1, String... sqPermissions) {
+    GsonRepositoryCollaborator collaborator = new GsonRepositoryCollaborator(collaboratorLogin, id, role1,
+      new GsonRepositoryPermissions(false, false, false, false, false));
+    mockPermissionsConversion(collaborator, sqPermissions);
+    return collaborator;
+  }
+
+  private void mockGithubCollaboratorsFromApi(GsonRepositoryCollaborator... repositoryCollaborators) {
+    Set<GsonRepositoryCollaborator> collaborators = Arrays.stream(repositoryCollaborators).collect(toSet());
+    when(githubApplicationClient.getRepositoryCollaborators(DEVOPS_PROJECT_DESCRIPTOR.url(), appInstallationToken, ORGANIZATION_NAME, REPOSITORY_NAME)).thenReturn(collaborators);
+  }
+
+  private GsonRepositoryTeam mockGithubTeam(String name, int id, String role, String... sqPermissions) {
+    GsonRepositoryTeam gsonRepositoryTeam = new GsonRepositoryTeam(name, id, name + "slug", role, new GsonRepositoryPermissions(false, false, false, false, false));
+    mockPermissionsConversion(gsonRepositoryTeam, sqPermissions);
+    return gsonRepositoryTeam;
+  }
+
+  private void mockTeamsFromApi(GsonRepositoryTeam... repositoryTeams) {
+    when(githubApplicationClient.getRepositoryTeams(DEVOPS_PROJECT_DESCRIPTOR.url(), appInstallationToken, ORGANIZATION_NAME, REPOSITORY_NAME))
+      .thenReturn(Arrays.stream(repositoryTeams).collect(toSet()));
+  }
+
+  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 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 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);
+  }
+
+  @Test
+  public void createProjectAndBindToDevOpsPlatform_whenRepoNotFound_throws() {
+    assertThatIllegalStateException().isThrownBy(
+        () -> githubProjectCreator.createProjectAndBindToDevOpsPlatform(mock(), SCANNER_API_DEVOPS_AUTO_CONFIG, null))
+      .withMessage("Impossible to find the repository 'orga2/repo1' on GitHub, using the devops config " + ALM_SETTING_KEY);
+  }
+
+  @Test
+  public void createProjectAndBindToDevOpsPlatformFromScanner_whenRepoFoundOnGitHub_successfullyCreatesProject() {
+    // given
+    mockGitHubRepository();
+
+    ComponentCreationData componentCreationData = mockProjectCreation("generated_orga2/repo1");
+    ProjectAlmSettingDao projectAlmSettingDao = mock();
+    when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao);
+
+    // when
+    ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true),
+      SCANNER_API_DEVOPS_AUTO_CONFIG, null);
+
+    // then
+    assertThat(actualComponentCreationData).isEqualTo(componentCreationData);
+
+    ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue();
+    assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters, "generated_orga2/repo1", SCANNER_API_DEVOPS_AUTO_CONFIG);
+    assertThat(componentCreationParameters.isManaged()).isFalse();
+    assertThat(componentCreationParameters.newComponent().isPrivate()).isFalse();
+
+    verify(projectAlmSettingDao).insertOrUpdate(any(), projectAlmSettingDtoCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq("generated_orga2/repo1"));
+    ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue();
+    assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto);
+  }
+
+  @Test
+  public void createProjectAndBindToDevOpsPlatformFromApi_whenRepoFoundOnGitHub_successfullyCreatesProject() {
+    // given
+    String projectKey = "customProjectKey";
+    mockGitHubRepository();
+
+    ComponentCreationData componentCreationData = mockProjectCreation(projectKey);
+    ProjectAlmSettingDao projectAlmSettingDao = mock();
+    when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao);
+
+    // when
+    ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), ALM_IMPORT_API, projectKey);
+
+    // then
+    assertThat(actualComponentCreationData).isEqualTo(componentCreationData);
+
+    ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue();
+    assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters, projectKey, ALM_IMPORT_API);
+    assertThat(componentCreationParameters.isManaged()).isFalse();
+    assertThat(componentCreationParameters.newComponent().isPrivate()).isFalse();
+
+    verify(projectAlmSettingDao).insertOrUpdate(any(), projectAlmSettingDtoCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq(projectKey));
+    ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue();
+    assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto);
+  }
+
+  public void createProjectAndBindToDevOpsPlatformFromApi_whenRepoFoundOnGitHubAutoProvisioningOnAndRepoPrivate_successfullyCreatesProject() {
+    // given
+    when(githubProjectCreationParameters.projectsArePrivateByDefault()).thenReturn(true);
+    when(githubProjectCreationParameters.isProvisioningEnabled()).thenReturn(true);
+
+    String projectKey = "customProjectKey";
+    mockGitHubRepository();
+
+    ComponentCreationData componentCreationData = mockProjectCreation(projectKey);
+    ProjectAlmSettingDao projectAlmSettingDao = mock();
+    when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao);
+
+    // when
+    ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), ALM_IMPORT_API, projectKey);
+
+    // then
+    assertThat(actualComponentCreationData).isEqualTo(componentCreationData);
+
+    ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue();
+    assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters, projectKey, ALM_IMPORT_API);
+    assertThat(componentCreationParameters.isManaged()).isTrue();
+    assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue();
+
+    verify(projectAlmSettingDao).insertOrUpdate(any(), projectAlmSettingDtoCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq(projectKey));
+    ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue();
+    assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto);
+  }
+
+  private void mockGitHubRepository() {
+    GithubApplicationClient.Repository repository = mock();
+    when(repository.getDefaultBranch()).thenReturn(MAIN_BRANCH_NAME);
+    when(repository.getName()).thenReturn(REPOSITORY_NAME);
+    when(repository.getFullName()).thenReturn(DEVOPS_PROJECT_DESCRIPTOR.projectIdentifier());
+    when(githubApplicationClient.getRepository(DEVOPS_PROJECT_DESCRIPTOR.url(), appInstallationToken, DEVOPS_PROJECT_DESCRIPTOR.projectIdentifier())).thenReturn(
+      Optional.of(repository));
+    when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn("generated_" + DEVOPS_PROJECT_DESCRIPTOR.projectIdentifier());
+  }
+
+  private ComponentCreationData mockProjectCreation(String projectKey) {
+    ComponentCreationData componentCreationData = mock();
+    ProjectDto projectDto = mockProjectDto(projectKey);
+    when(componentCreationData.projectDto()).thenReturn(projectDto);
+    when(componentUpdater.createWithoutCommit(any(), componentCreationParametersCaptor.capture())).thenReturn(componentCreationData);
+    return componentCreationData;
+  }
+
+  private static ProjectDto mockProjectDto(String projectKey) {
+    ProjectDto projectDto = mock();
+    when(projectDto.getName()).thenReturn(REPOSITORY_NAME);
+    when(projectDto.getKey()).thenReturn(projectKey);
+    when(projectDto.getUuid()).thenReturn("project-uuid-1");
+    return projectDto;
+  }
+
+  private static void assertComponentCreationParametersContainsCorrectInformation(ComponentCreationParameters componentCreationParameters, String expectedKey,
+    CreationMethod expectedCreationMethod) {
+    assertThat(componentCreationParameters.creationMethod()).isEqualTo(expectedCreationMethod);
+    assertThat(componentCreationParameters.mainBranchName()).isEqualTo(MAIN_BRANCH_NAME);
+    assertThat(componentCreationParameters.userLogin()).isEqualTo(USER_LOGIN);
+    assertThat(componentCreationParameters.userUuid()).isEqualTo(USER_UUID);
+
+    NewComponent newComponent = componentCreationParameters.newComponent();
+    assertThat(newComponent.isProject()).isTrue();
+    assertThat(newComponent.qualifier()).isEqualTo(Qualifiers.PROJECT);
+    assertThat(newComponent.key()).isEqualTo(expectedKey);
+    assertThat(newComponent.name()).isEqualTo(REPOSITORY_NAME);
+  }
+
+  private static void assertAlmSettingsDtoContainsCorrectInformation(AlmSettingDto almSettingDto, ProjectDto projectDto, ProjectAlmSettingDto projectAlmSettingDto) {
+    assertThat(projectAlmSettingDto.getAlmRepo()).isEqualTo(DEVOPS_PROJECT_DESCRIPTOR.projectIdentifier());
+    assertThat(projectAlmSettingDto.getAlmSlug()).isNull();
+    assertThat(projectAlmSettingDto.getAlmSettingUuid()).isEqualTo(almSettingDto.getUuid());
+    assertThat(projectAlmSettingDto.getProjectUuid()).isEqualTo(projectDto.getUuid());
+    assertThat(projectAlmSettingDto.getMonorepo()).isFalse();
+    assertThat(projectAlmSettingDto.getSummaryCommentEnabled()).isTrue();
+  }
+}
index 4f9c29e96b3763532071d94ef57eafdf8b73775e..06af9331eb13b755d9199f9a825208b7af4ab2ac 100644 (file)
@@ -63,8 +63,8 @@ import org.sonar.server.almintegration.ws.ProjectKeyGenerator;
 import org.sonar.server.almintegration.ws.github.GithubProvisioningWs;
 import org.sonar.server.almsettings.MultipleAlmFeature;
 import org.sonar.server.almsettings.ws.AlmSettingsWsModule;
-import org.sonar.server.almsettings.ws.DelegatingDevOpsPlatformService;
-import org.sonar.server.almsettings.ws.GitHubDevOpsPlatformService;
+import org.sonar.server.almsettings.ws.DelegatingDevOpsProjectCreatorFactory;
+import org.sonar.server.almsettings.ws.GithubProjectCreatorFactory;
 import org.sonar.server.authentication.AuthenticationModule;
 import org.sonar.server.authentication.DefaultAdminCredentialsVerifierImpl;
 import org.sonar.server.authentication.DefaultAdminCredentialsVerifierNotificationHandler;
@@ -310,7 +310,7 @@ public class PlatformLevel4 extends PlatformLevel {
       DefaultBranchNameResolver.class,
       DefaultDocumentationLinkGenerator.class,
       DelegatingManagedServices.class,
-      DelegatingDevOpsPlatformService.class,
+      DelegatingDevOpsProjectCreatorFactory.class,
 
       // batch
       new BatchWsModule(),
@@ -552,7 +552,7 @@ public class PlatformLevel4 extends PlatformLevel {
       GithubApplicationHttpClientImpl.class,
       GithubProvisioningConfigValidator.class,
       GithubProvisioningWs.class,
-      GitHubDevOpsPlatformService.class,
+      GithubProjectCreatorFactory.class,
       GithubPermissionConverter.class,
       BitbucketCloudRestClientConfiguration.class,
       BitbucketServerRestClient.class,