From b4d840ef7e0f56c3eb69bee457d2e3d43669759c Mon Sep 17 00:00:00 2001 From: Aurelien Poscia Date: Wed, 18 Oct 2023 14:57:13 +0200 Subject: [PATCH] SONAR-20700 Use auth App installation token to check permissions on GH --- .../github/ImportGithubProjectActionIT.java | 50 +++++++++++++------ .../server/ce/queue/ReportSubmitterIT.java | 30 ++++++++--- .../ws/GithubProjectCreationParameters.java | 7 ++- .../almsettings/ws/GithubProjectCreator.java | 23 ++++++--- .../ws/GithubProjectCreatorFactory.java | 41 ++++++++++----- .../server/ce/queue/ReportSubmitter.java | 4 ++ .../ws/GithubProjectCreatorFactoryTest.java | 28 +++++++---- .../ws/GithubProjectCreatorTest.java | 26 ++++++---- 8 files changed, 147 insertions(+), 62 deletions(-) diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java index 78c3c9ec303..53074f8f11b 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java @@ -25,13 +25,16 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.sonar.alm.client.github.AppInstallationToken; import org.sonar.alm.client.github.GithubApplicationClient; import org.sonar.alm.client.github.GithubApplicationClientImpl; +import org.sonar.alm.client.github.api.GsonRepositoryCollaborator; import org.sonar.api.resources.Qualifiers; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.System2; import org.sonar.auth.github.GitHubSettings; import org.sonar.auth.github.GithubPermissionConverter; +import org.sonar.auth.github.GsonRepositoryPermissions; import org.sonar.core.i18n.I18n; import org.sonar.core.platform.EditionProvider; import org.sonar.core.platform.PlatformEditionProvider; @@ -131,7 +134,7 @@ public class ImportGithubProjectActionIT { private final ManagedProjectService managedProjectService = mock(ManagedProjectService.class); - private final GithubPermissionConverter githubPermissionConverter = new GithubPermissionConverter(); + private final GithubPermissionConverter githubPermissionConverter = mock(); 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, @@ -147,7 +150,7 @@ public class ImportGithubProjectActionIT { public void importProject_ifProjectWithSameNameDoesNotExist_importSucceed() { AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings(); - GithubApplicationClient.Repository repository = mockGithubInteractions(); + GithubApplicationClient.Repository repository = mockGithubDevOpsAppInteractions(); Projects.CreateWsResponse response = callWebService(githubAlmSetting); @@ -171,7 +174,7 @@ public class ImportGithubProjectActionIT { AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings(); - mockGithubInteractions(); + mockGithubDevOpsAppInteractions(); Projects.CreateWsResponse response = ws.newRequest() .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey()) @@ -199,7 +202,7 @@ public class ImportGithubProjectActionIT { AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings(); - mockGithubInteractions(); + mockGithubDevOpsAppInteractions(); Projects.CreateWsResponse response = ws.newRequest() .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey()) @@ -230,7 +233,7 @@ public class ImportGithubProjectActionIT { AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings(); - mockGithubInteractions(); + mockGithubDevOpsAppInteractions(); Projects.CreateWsResponse response = ws.newRequest() .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey()) @@ -257,7 +260,7 @@ public class ImportGithubProjectActionIT { AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings(); - mockGithubInteractions(); + mockGithubDevOpsAppInteractions(); Projects.CreateWsResponse response = ws.newRequest() .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey()) @@ -283,7 +286,7 @@ public class ImportGithubProjectActionIT { AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings(); db.components().insertPublicProject(p -> p.setKey("Hello-World")).getMainBranchComponent(); - GithubApplicationClient.Repository repository = mockGithubInteractions(); + GithubApplicationClient.Repository repository = mockGithubDevOpsAppInteractions(); Projects.CreateWsResponse response = ws.newRequest() .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey()) @@ -299,7 +302,7 @@ public class ImportGithubProjectActionIT { public void importProject_whenGithubProvisioningIsDisabled_shouldApplyPermissionTemplate() { AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings(); - mockGithubInteractions(); + mockGithubDevOpsAppInteractions(); when(gitHubSettings.isProvisioningEnabled()).thenReturn(false); ws.newRequest() @@ -318,8 +321,9 @@ public class ImportGithubProjectActionIT { public void importProject_whenGithubProvisioningIsEnabled_shouldNotApplyPermissionTemplate() { AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings(); - mockGithubInteractions(); when(gitHubSettings.isProvisioningEnabled()).thenReturn(true); + mockGithubDevOpsAppInteractions(); + mockGithubAuthAppInteractions(); ws.newRequest() .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey()) @@ -330,10 +334,29 @@ public class ImportGithubProjectActionIT { } + private void mockGithubAuthAppInteractions() { + when(gitHubSettings.appId()).thenReturn("432"); + when(gitHubSettings.privateKey()).thenReturn("private key"); + when(gitHubSettings.apiURL()).thenReturn("http://www.url.com"); + + AppInstallationToken appInstallationToken = mock(); + + when(appClient.getInstallationId(any(), any())).thenReturn(Optional.of(321L)); + when(appClient.createAppInstallationToken(any(), eq(321L))).thenReturn(Optional.of(appInstallationToken)); + + GsonRepositoryCollaborator gsonRepositoryCollaborator = new GsonRepositoryCollaborator("toto", 2, "admin", new GsonRepositoryPermissions(true, true, true, true, true)); + when(appClient.getRepositoryCollaborators(gitHubSettings.apiURL(), appInstallationToken, "octocat", PROJECT_KEY_NAME)).thenReturn(Set.of(gsonRepositoryCollaborator)); + + String role = gsonRepositoryCollaborator.roleName(); + GsonRepositoryPermissions permissions = gsonRepositoryCollaborator.permissions(); + when(githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(Set.of(), role, permissions)).thenReturn(Set.of("scan")); + + } + @Test public void importProject_shouldSetCreationMethodToApi_ifNonBrowserRequest() { AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings(); - mockGithubInteractions(); + mockGithubDevOpsAppInteractions(); Projects.CreateWsResponse response = callWebService(githubAlmSetting); @@ -346,7 +369,7 @@ public class ImportGithubProjectActionIT { public void importProject_shouldSetCreationMethodToBrowser_ifBrowserRequest() { AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings(); userSession.flagSessionAsGui(); - mockGithubInteractions(); + mockGithubDevOpsAppInteractions(); Projects.CreateWsResponse response = callWebService(githubAlmSetting); @@ -398,8 +421,7 @@ public class ImportGithubProjectActionIT { public void importProject_whenNoAlmSettingKeyAndOnlyOneConfig_shouldImport() { AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings(); - mockGithubInteractions(); - when(gitHubSettings.isProvisioningEnabled()).thenReturn(true); + mockGithubDevOpsAppInteractions(); TestRequest request = ws.newRequest() .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey()) @@ -415,7 +437,7 @@ public class ImportGithubProjectActionIT { .executeProtobuf(Projects.CreateWsResponse.class); } - private GithubApplicationClient.Repository mockGithubInteractions() { + private GithubApplicationClient.Repository mockGithubDevOpsAppInteractions() { GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false, "octocat/" + PROJECT_KEY_NAME, "https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, "default-branch"); diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ce/queue/ReportSubmitterIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ce/queue/ReportSubmitterIT.java index dd71c29014f..b56e6f13101 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ce/queue/ReportSubmitterIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ce/queue/ReportSubmitterIT.java @@ -53,8 +53,8 @@ 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.GithubProjectCreator; import org.sonar.server.almsettings.ws.GithubProjectCreationParameters; +import org.sonar.server.almsettings.ws.GithubProjectCreator; import org.sonar.server.almsettings.ws.GithubProjectCreatorFactory; import org.sonar.server.component.ComponentUpdater; import org.sonar.server.es.TestIndexers; @@ -263,22 +263,33 @@ public class ReportSubmitterIT { @Test public void submit_whenReportIsForANewProjectWithoutDevOpsMetadata_createsLocalProject() { - userSession.addPermission(GlobalPermission.SCAN).addPermission(PROVISION_PROJECTS); + userSession.addPermission(PROVISION_PROJECTS); mockSuccessfulPrepareSubmitCall(); - when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), any(), eq(PROJECT_KEY))) - .thenReturn(true); + when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), any(), eq(PROJECT_KEY))).thenReturn(true); underTest.submit(PROJECT_KEY, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}", UTF_8)); assertLocalProjectWasCreated(); } - @Test public void submit_whenReportIsForANewProjectWithValidAlmSettingsAutoProvisioningOnAndPermOnGh_createsProjectWithBinding() { + userSession.addPermission(PROVISION_PROJECTS); + when(managedInstanceService.isInstanceExternallyManaged()).thenReturn(true); + mockSuccessfulPrepareSubmitCall(); + + DevOpsProjectCreator devOpsProjectCreator = mockAlmSettingDtoAndDevOpsProjectCreator(CHARACTERISTICS); + doReturn(true).when(devOpsProjectCreator).isScanAllowedUsingPermissionsFromDevopsPlatform(); + + underTest.submit(PROJECT_KEY, PROJECT_NAME, CHARACTERISTICS, IOUtils.toInputStream("{binary}", UTF_8)); + + assertProjectWasCreatedWithBinding(); + } + + @Test + public void submit_whenReportIsForANewProjectWithValidAlmSettingsAutoProvisioningOnNoPermOnGhAndGlobalScanPerm_createsProjectWithBinding() { userSession.addPermission(GlobalPermission.SCAN).addPermission(PROVISION_PROJECTS); when(managedInstanceService.isInstanceExternallyManaged()).thenReturn(true); - when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), any(), eq(PROJECT_KEY))).thenReturn(true); mockSuccessfulPrepareSubmitCall(); DevOpsProjectCreator devOpsProjectCreator = mockAlmSettingDtoAndDevOpsProjectCreator(CHARACTERISTICS); @@ -345,8 +356,11 @@ public class ReportSubmitterIT { 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)); + GithubProjectCreationParameters githubProjectCreationParameters = new GithubProjectCreationParameters(projectDescriptor, almSettingDto, true, + managedInstanceService.isInstanceExternallyManaged(), userSession, mock(), + null); + DevOpsProjectCreator devOpsProjectCreator = spy( + new GithubProjectCreator(db.getDbClient(), githubApplicationClient, null, projectKeyGenerator, componentUpdater, githubProjectCreationParameters)); doReturn(Optional.of(devOpsProjectCreator)).when(devOpsProjectCreatorFactorySpy).getDevOpsProjectCreator(any(), eq(characteristics)); return devOpsProjectCreator; } 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 index 3f51f1bb297..9803f10f08c 100644 --- 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 @@ -19,10 +19,13 @@ */ package org.sonar.server.almsettings.ws; +import javax.annotation.Nullable; +import org.sonar.alm.client.github.AppInstallationToken; 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) { +public record GithubProjectCreationParameters(DevOpsProjectDescriptor devOpsProjectDescriptor, AlmSettingDto almSettingDto, boolean projectsArePrivateByDefault, + boolean isProvisioningEnabled, UserSession userSession, AccessToken devOpsAppInstallationToken, + @Nullable AppInstallationToken authAppInstallationToken) { } 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 index 20a608c353d..303ecdd991e 100644 --- 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 @@ -21,7 +21,9 @@ package org.sonar.server.almsettings.ws; import java.util.Optional; import java.util.Set; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; +import org.sonar.alm.client.github.AppInstallationToken; import org.sonar.alm.client.github.GithubApplicationClient; import org.sonar.alm.client.github.api.GsonRepositoryCollaborator; import org.sonar.alm.client.github.api.GsonRepositoryTeam; @@ -47,6 +49,7 @@ 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.api.utils.Preconditions.checkState; import static org.sonar.server.component.NewComponent.newComponentBuilder; public class GithubProjectCreator implements DevOpsProjectCreator { @@ -58,9 +61,12 @@ public class GithubProjectCreator implements DevOpsProjectCreator { private final ComponentUpdater componentUpdater; private final GithubProjectCreationParameters githubProjectCreationParameters; private final DevOpsProjectDescriptor devOpsProjectDescriptor; - private final AccessToken appInstallationToken; private final UserSession userSession; private final AlmSettingDto almSettingDto; + private final AccessToken devOpsAppInstallationToken; + + @CheckForNull + private final AppInstallationToken authAppInstallationToken; public GithubProjectCreator(DbClient dbClient, GithubApplicationClient githubApplicationClient, GithubPermissionConverter githubPermissionConverter, ProjectKeyGenerator projectKeyGenerator, ComponentUpdater componentUpdater, GithubProjectCreationParameters githubProjectCreationParameters) { @@ -71,14 +77,17 @@ public class GithubProjectCreator implements DevOpsProjectCreator { this.projectKeyGenerator = projectKeyGenerator; this.componentUpdater = componentUpdater; this.githubProjectCreationParameters = githubProjectCreationParameters; - devOpsProjectDescriptor = githubProjectCreationParameters.devOpsProjectDescriptor(); - appInstallationToken = githubProjectCreationParameters.appInstallationToken(); userSession = githubProjectCreationParameters.userSession(); almSettingDto = githubProjectCreationParameters.almSettingDto(); + devOpsProjectDescriptor = githubProjectCreationParameters.devOpsProjectDescriptor(); + devOpsAppInstallationToken = githubProjectCreationParameters.devOpsAppInstallationToken(); + authAppInstallationToken = githubProjectCreationParameters.authAppInstallationToken(); } @Override public boolean isScanAllowedUsingPermissionsFromDevopsPlatform() { + checkState(githubProjectCreationParameters.authAppInstallationToken() != null, "An auth app token is required in case repository permissions checking is necessary."); + String[] orgaAndRepoTokenified = devOpsProjectDescriptor.projectIdentifier().split("/"); String organization = orgaAndRepoTokenified[0]; String repository = orgaAndRepoTokenified[1]; @@ -93,8 +102,8 @@ public class GithubProjectCreator implements DevOpsProjectCreator { } private boolean doesUserHaveScanPermission(String organization, String repository, Set permissionsMappingDtos) { - Set repositoryCollaborators = githubApplicationClient.getRepositoryCollaborators(devOpsProjectDescriptor.url(), appInstallationToken, organization, - repository); + Set repositoryCollaborators = githubApplicationClient.getRepositoryCollaborators(devOpsProjectDescriptor.url(), authAppInstallationToken, + organization, repository); String externalLogin = userSession.getExternalIdentity().map(UserSession.ExternalIdentity::login).orElse(null); if (externalLogin == null) { @@ -109,7 +118,7 @@ public class GithubProjectCreator implements DevOpsProjectCreator { private boolean doesUserBelongToAGroupWithScanPermission(String organization, String repository, Set permissionsMappingDtos) { - Set repositoryTeams = githubApplicationClient.getRepositoryTeams(devOpsProjectDescriptor.url(), appInstallationToken, organization, repository); + Set repositoryTeams = githubApplicationClient.getRepositoryTeams(devOpsProjectDescriptor.url(), authAppInstallationToken, organization, repository); Set groupsOfUser = findUserMembershipOnSonarQube(organization); return repositoryTeams.stream() @@ -135,7 +144,7 @@ public class GithubProjectCreator implements DevOpsProjectCreator { @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()) + GithubApplicationClient.Repository repository = githubApplicationClient.getRepository(url, devOpsAppInstallationToken, devOpsProjectDescriptor.projectIdentifier()) .orElseThrow(() -> new IllegalStateException( String.format("Impossible to find the repository '%s' on GitHub, using the devops config %s", devOpsProjectDescriptor.projectIdentifier(), almSettingDto.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 index 59ca84e9417..c7c57272c1d 100644 --- 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 @@ -104,32 +104,49 @@ public class GithubProjectCreatorFactory implements DevOpsProjectCreatorFactory .map(appInstallationToken -> createGithubProjectCreator(dbSession, devOpsProjectDescriptor, almSettingDto, appInstallationToken)); } - private Optional 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()); + Optional authAppInstallationToken = getAuthAppInstallationTokenIfNecessary(devOpsProjectDescriptor); + GithubProjectCreationParameters githubProjectCreationParameters = new GithubProjectCreationParameters(devOpsProjectDescriptor, - almSettingDto, appInstallationToken, projectDefaultVisibility.get(dbSession).isPrivate(), gitHubSettings.isProvisioningEnabled(), userSession); + almSettingDto, projectDefaultVisibility.get(dbSession).isPrivate(), gitHubSettings.isProvisioningEnabled(), userSession, appInstallationToken, + authAppInstallationToken.orElse(null)); return new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, componentUpdater, githubProjectCreationParameters); } public Optional getDevOpsProjectCreator(DbSession dbSession, AlmSettingDto almSettingDto, AccessToken accessToken, DevOpsProjectDescriptor devOpsProjectDescriptor) { + + Optional authAppInstallationToken = getAuthAppInstallationTokenIfNecessary(devOpsProjectDescriptor); GithubProjectCreationParameters githubProjectCreationParameters = new GithubProjectCreationParameters(devOpsProjectDescriptor, - almSettingDto, accessToken, projectDefaultVisibility.get(dbSession).isPrivate(), gitHubSettings.isProvisioningEnabled(), userSession); + almSettingDto, projectDefaultVisibility.get(dbSession).isPrivate(), gitHubSettings.isProvisioningEnabled(), userSession, accessToken, authAppInstallationToken.orElse(null)); return Optional.of( new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, componentUpdater, githubProjectCreationParameters) ); } + private Optional getAuthAppInstallationTokenIfNecessary(DevOpsProjectDescriptor devOpsProjectDescriptor) { + if (gitHubSettings.isProvisioningEnabled()) { + GithubAppConfiguration githubAppConfiguration = new GithubAppConfiguration(Long.parseLong(gitHubSettings.appId()), gitHubSettings.privateKey(), gitHubSettings.apiURL()); + long installationId = findInstallationIdToAccessRepo(githubAppConfiguration, devOpsProjectDescriptor.projectIdentifier()) + .orElseThrow(() -> new IllegalStateException(format("GitHub auto-provisioning is activated. However the repo %s is not in the scope of the authentication application. " + + "The permissions can't be checked, and the project can not be breated.", + devOpsProjectDescriptor.projectIdentifier()))); + return Optional.of(generateAppInstallationToken(githubAppConfiguration, installationId)); + } + return Optional.empty(); + } + + private Optional 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))); + } + } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/queue/ReportSubmitter.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/queue/ReportSubmitter.java index 4095ba195b2..157162b093e 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/queue/ReportSubmitter.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/queue/ReportSubmitter.java @@ -52,6 +52,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.permission.GlobalPermission.SCAN; 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; @@ -183,6 +184,9 @@ public class ReportSubmitter { } private boolean wouldCurrentUserHaveScanPermission(String projectKey, DbSession dbSession, @Nullable DevOpsProjectCreator devOpsProjectCreator) { + if (userSession.hasPermission(SCAN)) { + return true; + } if (managedInstanceService.isInstanceExternallyManaged() && devOpsProjectCreator != null) { return devOpsProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform(); } 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 index acb20e3d19e..53afeb69800 100644 --- 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 @@ -99,6 +99,8 @@ public class GithubProjectCreatorFactoryTest { @Mock private AppInstallationToken appInstallationToken; + @Mock + private AppInstallationToken authAppInstallationToken; @InjectMocks private GithubProjectCreatorFactory githubProjectCreatorFactory; @@ -143,13 +145,13 @@ public class GithubProjectCreatorFactoryTest { 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)"); + .withMessage("Error while generating token for GitHub Api Url null (installation id: 534534534543)"); } @Test public void getDevOpsProjectCreator_whenOneValidAlmSetting_shouldInstantiateDevOpsProjectCreator() { AlmSettingDto almSettingDto = mockAlmSettingDto(true); - mockSuccesfullGithubInteraction(); + mockSuccessfulGithubInteraction(); DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES).orElseThrow(); @@ -158,12 +160,19 @@ public class GithubProjectCreatorFactoryTest { } @Test - public void getDevOpsProjectCreator_whenOneValidAlmSettingAndPublicByDefaultAndAutoProviisoningEnabled_shouldInstantiateDevOpsProjectCreator() { + public void getDevOpsProjectCreator_whenOneValidAlmSettingAndPublicByDefaultAndAutoProvisioningEnabled_shouldInstantiateDevOpsProjectCreatorAndDefineAnAuthAppToken() { + AlmSettingDto almSettingDto = mockAlmSettingDto(true); + mockSuccessfulGithubInteraction(); + when(projectDefaultVisibility.get(any()).isPrivate()).thenReturn(true); when(gitHubSettings.isProvisioningEnabled()).thenReturn(true); + when(gitHubSettings.appId()).thenReturn("4324"); + when(gitHubSettings.privateKey()).thenReturn("privateKey"); + when(gitHubSettings.apiURL()).thenReturn(GITHUB_API_URL); - AlmSettingDto almSettingDto = mockAlmSettingDto(true); - mockSuccesfullGithubInteraction(); + long authAppInstallationId = 32; + when(githubApplicationClient.getInstallationId(any(), eq(GITHUB_REPO_FULL_NAME))).thenReturn(Optional.of(authAppInstallationId)); + when(githubApplicationClient.createAppInstallationToken(any(), eq(authAppInstallationId))).thenReturn(Optional.of(authAppInstallationToken)); DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES).orElseThrow(); @@ -177,7 +186,7 @@ public class GithubProjectCreatorFactoryTest { AlmSettingDto notMatchingAlmSettingDto = mockAlmSettingDto(false); when(dbClient.almSettingDao().selectByAlm(dbSession, ALM.GITHUB)).thenReturn(List.of(notMatchingAlmSettingDto, matchingAlmSettingDto)); - mockSuccesfullGithubInteraction(); + mockSuccessfulGithubInteraction(); DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES).orElseThrow(); @@ -189,7 +198,7 @@ public class GithubProjectCreatorFactoryTest { public void getDevOpsProjectCreatorFromImport_shouldInstantiateDevOpsProjectCreator() { AlmSettingDto mockAlmSettingDto = mockAlmSettingDto(true); - mockSuccesfullGithubInteraction(); + mockSuccessfulGithubInteraction(); DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, mockAlmSettingDto, appInstallationToken, GITHUB_PROJECT_DESCRIPTOR) .orElseThrow(); @@ -198,15 +207,16 @@ public class GithubProjectCreatorFactoryTest { assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedGithubProjectCreator); } - private void mockSuccesfullGithubInteraction() { + private void mockSuccessfulGithubInteraction() { 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); + AppInstallationToken authAppInstallToken = isInstanceManaged ? authAppInstallationToken : null; GithubProjectCreationParameters githubProjectCreationParameters = new GithubProjectCreationParameters(devOpsProjectDescriptor, - almSettingDto, appInstallationToken, projectsArePrivateByDefault, isInstanceManaged, userSession); + almSettingDto, projectsArePrivateByDefault, isInstanceManaged, userSession, appInstallationToken, authAppInstallToken); return new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, componentUpdater, githubProjectCreationParameters); } 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 index 7cd0fce2246..7955bea449f 100644 --- 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 @@ -30,6 +30,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; 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.api.GsonRepositoryCollaborator; import org.sonar.alm.client.github.api.GsonRepositoryTeam; @@ -90,7 +91,9 @@ public class GithubProjectCreatorTest { @Mock private GithubProjectCreationParameters githubProjectCreationParameters; @Mock - private AccessToken appInstallationToken; + private AccessToken devOpsAppInstallationToken; + @Mock + private AppInstallationToken authAppInstallationToken; @Mock private UserSession userSession; @Mock @@ -113,18 +116,21 @@ public class GithubProjectCreatorTest { when(githubProjectCreationParameters.devOpsProjectDescriptor()).thenReturn(DEVOPS_PROJECT_DESCRIPTOR); when(githubProjectCreationParameters.userSession()).thenReturn(userSession); - when(githubProjectCreationParameters.appInstallationToken()).thenReturn(appInstallationToken); + when(githubProjectCreationParameters.devOpsAppInstallationToken()).thenReturn(devOpsAppInstallationToken); + when(githubProjectCreationParameters.authAppInstallationToken()).thenReturn(authAppInstallationToken); 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_whenNoAuthToken_throws() { + when(githubProjectCreationParameters.authAppInstallationToken()).thenReturn(null); + + assertThatIllegalStateException().isThrownBy(() -> githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()) + .withMessage("An auth app token is required in case repository permissions checking is necessary."); + } @Test public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenUserIsNotAGitHubUser_returnsFalse() { assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isFalse(); @@ -192,7 +198,7 @@ public class GithubProjectCreatorTest { private void mockGithubCollaboratorsFromApi(GsonRepositoryCollaborator... repositoryCollaborators) { Set collaborators = Arrays.stream(repositoryCollaborators).collect(toSet()); - when(githubApplicationClient.getRepositoryCollaborators(DEVOPS_PROJECT_DESCRIPTOR.url(), appInstallationToken, ORGANIZATION_NAME, REPOSITORY_NAME)).thenReturn(collaborators); + when(githubApplicationClient.getRepositoryCollaborators(DEVOPS_PROJECT_DESCRIPTOR.url(), authAppInstallationToken, ORGANIZATION_NAME, REPOSITORY_NAME)).thenReturn(collaborators); } private GsonRepositoryTeam mockGithubTeam(String name, int id, String role, String... sqPermissions) { @@ -202,7 +208,7 @@ public class GithubProjectCreatorTest { } private void mockTeamsFromApi(GsonRepositoryTeam... repositoryTeams) { - when(githubApplicationClient.getRepositoryTeams(DEVOPS_PROJECT_DESCRIPTOR.url(), appInstallationToken, ORGANIZATION_NAME, REPOSITORY_NAME)) + when(githubApplicationClient.getRepositoryTeams(DEVOPS_PROJECT_DESCRIPTOR.url(), authAppInstallationToken, ORGANIZATION_NAME, REPOSITORY_NAME)) .thenReturn(Arrays.stream(repositoryTeams).collect(toSet())); } @@ -323,7 +329,7 @@ public class GithubProjectCreatorTest { 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( + when(githubApplicationClient.getRepository(DEVOPS_PROJECT_DESCRIPTOR.url(), devOpsAppInstallationToken, DEVOPS_PROJECT_DESCRIPTOR.projectIdentifier())).thenReturn( Optional.of(repository)); when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn("generated_" + DEVOPS_PROJECT_DESCRIPTOR.projectIdentifier()); } -- 2.39.5