import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
import org.sonar.auth.github.GitHubSettings;
+import org.sonar.auth.github.GithubPermissionConverter;
import org.sonar.core.i18n.I18n;
import org.sonar.core.platform.EditionProvider;
import org.sonar.core.platform.PlatformEditionProvider;
private final NewCodeDefinitionResolver newCodeDefinitionResolver = new NewCodeDefinitionResolver(db.getDbClient(), editionProvider);
private final ManagedProjectService managedProjectService = mock(ManagedProjectService.class);
+
+ private final GithubPermissionConverter githubPermissionConverter = new GithubPermissionConverter();
private final GitHubDevOpsPlatformService gitHubDevOpsPlatformService = new GitHubDevOpsPlatformService(db.getDbClient(),
- null, appClient, projectDefaultVisibility, projectKeyGenerator, userSession, componentUpdater, gitHubSettings);
+ null, appClient, projectDefaultVisibility, projectKeyGenerator, userSession, componentUpdater, gitHubSettings, githubPermissionConverter);
private final WsActionTester ws = new WsActionTester(new ImportGithubProjectAction(db.getDbClient(), managedProjectService, userSession,
componentUpdater, importHelper, newCodeDefinitionResolver, defaultBranchNameResolver, gitHubDevOpsPlatformService));
private final BranchSupport branchSupport = spy(new BranchSupport(branchSupportDelegate));
private final DevOpsPlatformService devOpsPlatformService = new GitHubDevOpsPlatformService(db.getDbClient(), null,
- null, projectDefaultVisibility, null, userSession, componentUpdater, null);
+ null, projectDefaultVisibility, null, userSession, componentUpdater, null, null);
private final ManagedInstanceService managedInstanceService = mock();
private final ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentUpdater, permissionTemplateService, db.getDbClient(), branchSupport,
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;
private final DevOpsPlatformService devOpsPlatformService = new DelegatingDevOpsPlatformService(
Set.of(new GitHubDevOpsPlatformService(db.getDbClient(), githubGlobalSettingsValidator,
- githubApplicationClient, projectDefaultVisibility, projectKeyGenerator, userSession, componentUpdater, gitHubSettings)));
+ githubApplicationClient, projectDefaultVisibility, projectKeyGenerator, userSession, componentUpdater, gitHubSettings, null)));
private final DevOpsPlatformService devOpsPlatformServiceSpy = spy(devOpsPlatformService);
}
@Test
- public void submit_whenReportIsForANewProjectWithoutValidAlmSettings_createsProjectWithoutDevOpsBinding() {
+ public void submit_whenReportIsForANewGithubProjectWithoutValidAlmSettings_throws() {
userSession.addPermission(GlobalPermission.SCAN).addPermission(PROVISION_PROJECTS);
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), any(), eq(PROJECT_KEY))).thenReturn(true);
mockSuccessfulPrepareSubmitCall();
DevOpsProjectDescriptor projectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, "apiUrl", "orga/repo");
when(devOpsPlatformServiceSpy.getDevOpsProjectDescriptor(characteristics)).thenReturn(Optional.of(projectDescriptor));
- underTest.submit(PROJECT_KEY, PROJECT_NAME, characteristics, IOUtils.toInputStream("{binary}", UTF_8));
+ assertThatIllegalArgumentException().isThrownBy(() -> underTest.submit(PROJECT_KEY, PROJECT_NAME, characteristics, IOUtils.toInputStream("{binary}", UTF_8)))
+ .withMessage("The project orga/repo could not be created. It was auto-detected as a GITHUB project and no valid DevOps platform configuration were found to access apiUrl");
- assertLocalProjectWasCreated();
+ assertNoProjectWasCreated();
+ }
+
+ private void assertNoProjectWasCreated() {
+ assertThat(db.getDbClient().projectDao().selectAll(db.getSession())).isEmpty();
}
@Test
- public void submit_whenReportIsForANewProjectWithValidAlmSettingsButAutoProvisioningOn_createsLocalProject() {
+ public void submit_whenReportIsForANewProjectWithValidAlmSettingsAutoProvisioningOnAndPermOnGh_createsProjectWithBinding() {
userSession.addPermission(GlobalPermission.SCAN).addPermission(PROVISION_PROJECTS);
when(managedInstanceService.isInstanceExternallyManaged()).thenReturn(true);
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), any(), eq(PROJECT_KEY))).thenReturn(true);
Map<String, String> characteristics = Map.of("random", "data");
DevOpsProjectDescriptor projectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, "apiUrl", "orga/repo");
- mockInteractionsWithDevOpsPlatformServiceSpyBeforeProjectCreation(characteristics, projectDescriptor);
+ AlmSettingDto almSettingDto = mockInteractionsWithDevOpsPlatformServiceSpyBeforeProjectCreation(characteristics, projectDescriptor);
+ when(devOpsPlatformServiceSpy.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, projectDescriptor)).thenReturn(true);
underTest.submit(PROJECT_KEY, PROJECT_NAME, characteristics, IOUtils.toInputStream("{binary}", UTF_8));
+ assertProjectWasCreatedWithBinding();
+ }
+
+ @Test
+ public void submit_whenReportIsForANewProjectWithProjectDescriptorAndNoValidAlmSettingsAndAutoProvisioningOn_throws() {
+ userSession.addPermission(GlobalPermission.SCAN).addPermission(PROVISION_PROJECTS);
+ when(managedInstanceService.isInstanceExternallyManaged()).thenReturn(true);
+ when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), any(), eq(PROJECT_KEY))).thenReturn(true);
+ mockSuccessfulPrepareSubmitCall();
+
+ Map<String, String> characteristics = Map.of("random", "data");
+ DevOpsProjectDescriptor projectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, "apiUrl", "orga/repo");
+ doReturn(Optional.of(projectDescriptor)).when(devOpsPlatformServiceSpy).getDevOpsProjectDescriptor(characteristics);
+
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> underTest.submit(PROJECT_KEY, PROJECT_NAME, characteristics, IOUtils.toInputStream("{binary}", UTF_8)))
+ .withMessage("The project orga/repo could not be created. It was auto-detected as a GITHUB project and no valid DevOps platform configuration were found to access apiUrl");
+
+ assertNoProjectWasCreated();
+ }
+
+ @Test
+ public void submit_whenReportIsForANewProjectWithoutDevOpsMetadataAndAutoProvisioningOn_shouldCreateLocalProject() {
+ userSession.addPermission(GlobalPermission.SCAN).addPermission(PROVISION_PROJECTS);
+ when(managedInstanceService.isInstanceExternallyManaged()).thenReturn(true);
+ when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), any(), eq(PROJECT_KEY))).thenReturn(true);
+ mockSuccessfulPrepareSubmitCall();
+
+ Map<String, String> characteristics = Map.of("random", "data");
+
+ underTest.submit(PROJECT_KEY, PROJECT_NAME, characteristics, IOUtils.toInputStream("{binary}", UTF_8));
assertLocalProjectWasCreated();
}
assertThat(db.getDbClient().projectAlmSettingDao().selectByProject(db.getSession(), projectDto.getUuid())).isPresent();
}
- private void mockInteractionsWithDevOpsPlatformServiceSpyBeforeProjectCreation(Map<String, String> characteristics, DevOpsProjectDescriptor projectDescriptor) {
+ private AlmSettingDto mockInteractionsWithDevOpsPlatformServiceSpyBeforeProjectCreation(Map<String, String> characteristics, DevOpsProjectDescriptor projectDescriptor) {
doReturn(Optional.of(projectDescriptor)).when(devOpsPlatformServiceSpy).getDevOpsProjectDescriptor(characteristics);
AlmSettingDto almSettingDto = mock(AlmSettingDto.class);
when(almSettingDto.getAlm()).thenReturn(ALM.GITHUB);
when(almSettingDto.getUuid()).thenReturn("TEST_GH");
doReturn(Optional.of(almSettingDto)).when(devOpsPlatformServiceSpy).getValidAlmSettingDto(any(), eq(projectDescriptor));
mockGithubInteractions(almSettingDto);
+ return almSettingDto;
}
private void mockGithubInteractions(AlmSettingDto almSettingDto) {
.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) {
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,
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.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;
private final UserSession userSession;
private final ComponentUpdater componentUpdater;
private final GitHubSettings gitHubSettings;
+ private final GithubPermissionConverter githubPermissionConverter;
public GitHubDevOpsPlatformService(DbClient dbClient, GithubGlobalSettingsValidator githubGlobalSettingsValidator,
GithubApplicationClient githubApplicationClient, ProjectDefaultVisibility projectDefaultVisibility, ProjectKeyGenerator projectKeyGenerator, UserSession userSession,
- ComponentUpdater componentUpdater, GitHubSettings gitHubSettings) {
+ ComponentUpdater componentUpdater, GitHubSettings gitHubSettings, GithubPermissionConverter githubPermissionConverter) {
this.dbClient = dbClient;
this.githubGlobalSettingsValidator = githubGlobalSettingsValidator;
this.githubApplicationClient = githubApplicationClient;
this.userSession = userSession;
this.componentUpdater = componentUpdater;
this.gitHubSettings = gitHubSettings;
+ this.githubPermissionConverter = githubPermissionConverter;
}
@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(almSettingDto, devOpsProjectDescriptor.projectIdentifier()).isPresent())
+ .filter(almSettingDto -> findInstallationIdToAccessRepo(devOpsProjectDescriptor, almSettingDto))
.findFirst();
if (configurationToUse.isPresent()) {
LOG.info("DevOps configuration {} auto-detected", configurationToUse.get().getKey());
return configurationToUse;
}
+ private boolean findInstallationIdToAccessRepo(DevOpsProjectDescriptor devOpsProjectDescriptor, AlmSettingDto almSettingDto) {
+ GithubAppConfiguration githubAppConfiguration = githubGlobalSettingsValidator.validate(almSettingDto);
+ return findInstallationIdToAccessRepo(almSettingDto, githubAppConfiguration, devOpsProjectDescriptor.projectIdentifier()).isPresent();
+ }
+
+ @Override
+ public boolean isScanAllowedUsingPermissionsFromDevopsPlatform(AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor) {
+ GithubAppConfiguration githubAppConfiguration = githubGlobalSettingsValidator.validate(almSettingDto);
+ long installationId = findInstallationIdToAccessRepo(almSettingDto, githubAppConfiguration, devOpsProjectDescriptor.projectIdentifier())
+ .orElseThrow(() -> new IllegalStateException(format("Impossible to find the repository %s on GitHub, using the devops config %s.",
+ devOpsProjectDescriptor.projectIdentifier(), almSettingDto.getKey())));
+ return isScanAllowedUsingPermissionsFromGithub(devOpsProjectDescriptor.projectIdentifier(), githubAppConfiguration, installationId);
+ }
+
+ private boolean isScanAllowedUsingPermissionsFromGithub(String organizationAndRepository, GithubAppConfiguration githubAppConfiguration, long installationId) {
+ AppInstallationToken accessToken = generateAppInstallationToken(githubAppConfiguration, installationId);
+
+ String[] orgaAndRepoTokenified = organizationAndRepository.split("/");
+ String organization = orgaAndRepoTokenified[0];
+ String repository = orgaAndRepoTokenified[1];
+
+ Set<GithubPermissionsMappingDto> permissionsMappingDtos = dbClient.githubPermissionsMappingDao().findAll(dbClient.openSession(false));
+
+ boolean userHasDirectAccessToRepo = doesUserHaveScanPermission(githubAppConfiguration.getApiEndpoint(), accessToken, organization, repository, permissionsMappingDtos);
+ if (userHasDirectAccessToRepo) {
+ return true;
+ }
+ return doesUserBelongToAGroupWithScanPermission(githubAppConfiguration.getApiEndpoint(), accessToken, organization, repository, permissionsMappingDtos);
+ }
+
+ private boolean doesUserHaveScanPermission(String apiEndpoint, AppInstallationToken accessToken, String organization, String repository,
+ Set<GithubPermissionsMappingDto> permissionsMappingDtos) {
+ Set<GsonRepositoryCollaborator> repositoryCollaborators = githubApplicationClient.getRepositoryCollaborators(apiEndpoint, accessToken, organization, repository);
+
+ String externalLogin = userSession.getExternalIdentity().map(UserSession.ExternalIdentity::login).orElse(null);
+ if (externalLogin == null) {
+ return false;
+ }
+ return repositoryCollaborators.stream()
+ .filter(gsonRepositoryCollaborator -> externalLogin.equals(gsonRepositoryCollaborator.name()))
+ .findAny()
+ .map(gsonRepositoryCollaborator -> hasScanPermission(permissionsMappingDtos, gsonRepositoryCollaborator.roleName(), gsonRepositoryCollaborator.permissions()))
+ .orElse(false);
+ }
+
+ private boolean doesUserBelongToAGroupWithScanPermission(String apiUrl, AppInstallationToken accessToken, String organization, String repository,
+ Set<GithubPermissionsMappingDto> permissionsMappingDtos) {
+ Set<GsonRepositoryTeam> repositoryTeams = githubApplicationClient.getRepositoryTeams(apiUrl, accessToken, organization, repository);
+
+ Set<String> groupsOfUser = findUserMembershipOnSonarQube(organization);
+ return repositoryTeams.stream()
+ .filter(team -> hasScanPermission(permissionsMappingDtos, team.permission(), team.permissions()))
+ .map(GsonRepositoryTeam::name)
+ .anyMatch(groupsOfUser::contains);
+ }
+
+ private Set<String> findUserMembershipOnSonarQube(String organization) {
+ return userSession.getGroups().stream()
+ .map(GroupDto::getName)
+ .filter(groupName -> groupName.contains("/"))
+ .map(name -> name.replaceFirst(organization + "/", ""))
+ .collect(Collectors.toSet());
+ }
+
+ private boolean hasScanPermission(Set<GithubPermissionsMappingDto> permissionsMappingDtos, String role, GsonRepositoryPermissions permissions) {
+ Set<String> sonarqubePermissions = githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(permissionsMappingDtos,
+ role, permissions);
+ return sonarqubePermissions.contains(UserRole.SCAN);
+ }
+
@Override
public ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, String projectKey, AlmSettingDto almSettingDto,
DevOpsProjectDescriptor devOpsProjectDescriptor) {
GithubAppConfiguration githubAppConfiguration = githubGlobalSettingsValidator.validate(almSettingDto);
- GithubApplicationClient.Repository repository = findInstallationIdToAccessRepo(almSettingDto, devOpsProjectDescriptor.projectIdentifier())
+ GithubApplicationClient.Repository repository = findInstallationIdToAccessRepo(almSettingDto, githubAppConfiguration, devOpsProjectDescriptor.projectIdentifier())
.flatMap(installationId -> findRepositoryOnGithub(devOpsProjectDescriptor.projectIdentifier(), githubAppConfiguration, installationId))
.orElseThrow(() -> new IllegalStateException(format("Impossible to find the repository %s on GitHub, using the devops config %s.",
devOpsProjectDescriptor.projectIdentifier(), almSettingDto.getKey())));
return createProjectAndBindToDevOpsPlatform(dbSession, projectKey, almSettingDto, repository, SCANNER_API_DEVOPS_AUTO_CONFIG);
}
- private Optional<Long> findInstallationIdToAccessRepo(AlmSettingDto almSettingDto, String repositoryKey) {
+ private Optional<Long> findInstallationIdToAccessRepo(AlmSettingDto almSettingDto, GithubAppConfiguration githubAppConfiguration, String repositoryKey) {
try {
- GithubAppConfiguration githubAppConfiguration = githubGlobalSettingsValidator.validate(almSettingDto);
return githubApplicationClient.getInstallationId(githubAppConfiguration, repositoryKey);
} catch (Exception exception) {
LOG.info(format("Could not use DevOps configuration '%s' to access repo %s. Error: %s", almSettingDto.getKey(), repositoryKey, exception.getMessage()));
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;
// Project key is already used as a module of another project
ComponentDto anotherBaseProject = dbClient.componentDao().selectOrFailByUuid(dbSession, component.branchUuid());
errors.add(format("The project '%s' is already defined in SonarQube but as a module of project '%s'. "
- + "If you really want to stop directly analysing project '%s', please first delete it from SonarQube and then relaunch the analysis of project '%s'.",
+ + "If you really want to stop directly analysing project '%s', please first delete it from SonarQube and then relaunch the analysis of project '%s'.",
rawProjectKey, anotherBaseProject.getKey(), anotherBaseProject.getKey(), rawProjectKey));
}
if (!errors.isEmpty()) {
DbSession dbSession, BranchSupport.ComponentKey componentKey) {
userSession.checkPermission(GlobalPermission.PROVISION_PROJECTS);
- boolean wouldCurrentUserHaveScanPermission = permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(dbSession, userSession.getUuid(), projectKey);
- if (!wouldCurrentUserHaveScanPermission) {
- throw insufficientPrivilegesException();
+ DevOpsProjectDescriptor devOpsProjectDescriptor = devOpsPlatformService.getDevOpsProjectDescriptor(characteristics).orElse(null);
+ AlmSettingDto almSettingDto = getAlmSettingDto(dbSession, devOpsProjectDescriptor);
+
+ throwIfNoValidDevOpsConfigurationFoundForDevOpsProject(devOpsProjectDescriptor, almSettingDto);
+ throwIfCurrentUserWouldNotHaveScanPermission(projectKey, dbSession, devOpsProjectDescriptor, almSettingDto);
+
+ if (almSettingDto != null) {
+ return devOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, projectKey, almSettingDto, devOpsProjectDescriptor);
}
+ return createProject(dbSession, componentKey.getKey(), defaultIfBlank(projectName, projectKey));
+ }
- if (managedInstanceService.isInstanceExternallyManaged()) {
- return createProject(dbSession, componentKey.getKey(), defaultIfBlank(projectName, projectKey));
+ @CheckForNull
+ private AlmSettingDto getAlmSettingDto(DbSession dbSession, @Nullable DevOpsProjectDescriptor devOpsProjectDescriptor) {
+ if (devOpsProjectDescriptor != null) {
+ return devOpsPlatformService.getValidAlmSettingDto(dbSession, devOpsProjectDescriptor).orElse(null);
}
+ return null;
+ }
- Optional<DevOpsProjectDescriptor> devOpsProjectDescriptor = devOpsPlatformService.getDevOpsProjectDescriptor(characteristics);
- Optional<AlmSettingDto> almSettingDto = devOpsProjectDescriptor.flatMap(descriptor -> devOpsPlatformService.getValidAlmSettingDto(dbSession, descriptor));
- if (almSettingDto.isPresent()) {
- return devOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, projectKey, almSettingDto.get(), devOpsProjectDescriptor.get());
+ private static void throwIfNoValidDevOpsConfigurationFoundForDevOpsProject(@Nullable DevOpsProjectDescriptor devOpsProjectDescriptor, @Nullable AlmSettingDto almSettingDto) {
+ if (devOpsProjectDescriptor != null && almSettingDto == null) {
+ throw new IllegalArgumentException(format("The project %s could not be created. It was auto-detected as a %s project "
+ + "and no valid DevOps platform configuration were found to access %s",
+ devOpsProjectDescriptor.projectIdentifier(), devOpsProjectDescriptor.alm(), devOpsProjectDescriptor.url()));
}
- return createProject(dbSession, componentKey.getKey(), defaultIfBlank(projectName, projectKey));
+ }
+
+ private void throwIfCurrentUserWouldNotHaveScanPermission(String projectKey, DbSession dbSession, @Nullable DevOpsProjectDescriptor devOpsProjectDescriptor,
+ @Nullable AlmSettingDto almSettingDto) {
+ if (!wouldCurrentUserHaveScanPermission(projectKey, dbSession, devOpsProjectDescriptor, almSettingDto)) {
+ throw insufficientPrivilegesException();
+ }
+ }
+
+ private boolean wouldCurrentUserHaveScanPermission(String projectKey, DbSession dbSession, @Nullable DevOpsProjectDescriptor devOpsProjectDescriptor,
+ @Nullable AlmSettingDto almSettingDto) {
+ if (managedInstanceService.isInstanceExternallyManaged() && almSettingDto != null && devOpsProjectDescriptor != null) {
+ return devOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, devOpsProjectDescriptor);
+ }
+ return permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(dbSession, userSession.getUuid(), projectKey);
}
private ComponentCreationData createProject(DbSession dbSession, String projectKey, String projectName) {
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;
.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);
.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;
}
*/
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.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.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.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;
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 GitHubSettings gitHubSettings;
+ @Mock
+ private GithubPermissionConverter githubPermissionConverter;
+
@InjectMocks
private GitHubDevOpsPlatformService gitHubDevOpsPlatformService;
@Test
public void createProjectAndBindToDevOpsPlatform_whenRepoNotFound_throws() {
- DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, GITHUB_API_URL, GITHUB_REPO_FULL_NAME);
- AlmSettingDto almSettingDto = mockAlmSettingDto(devOpsProjectDescriptor);
+ AlmSettingDto almSettingDto = mockAlmSettingDto();
GithubAppConfiguration githubAppConfiguration = mockGitHubAppConfiguration(almSettingDto);
when(githubApplicationClient.getInstallationId(githubAppConfiguration, GITHUB_REPO_FULL_NAME)).thenReturn(Optional.empty());
assertThatIllegalStateException().isThrownBy(
- () -> gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, PROJECT_KEY, almSettingDto, devOpsProjectDescriptor))
+ () -> gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, PROJECT_KEY, almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR))
.withMessage("Impossible to find the repository orgname/projectName on GitHub, using the devops config devops-platform-config-1.");
}
@Test
public void createProjectAndBindToDevOpsPlatform_whenRepoFoundOnGitHub_successfullyCreatesProject() {
// given
- DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, GITHUB_API_URL, GITHUB_REPO_FULL_NAME);
-
- AlmSettingDto almSettingDto = mockAlmSettingDto(devOpsProjectDescriptor);
+ AlmSettingDto almSettingDto = mockAlmSettingDto();
mockExistingGitHubRepository(almSettingDto);
ComponentCreationData componentCreationData = mockProjectCreation();
// when
ComponentCreationData actualComponentCreationData = gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, PROJECT_KEY, almSettingDto,
- devOpsProjectDescriptor);
+ DEV_OPS_PROJECT_DESCRIPTOR);
// then
assertThat(actualComponentCreationData).isEqualTo(componentCreationData);
@Test
public void createProjectAndBindToDevOpsPlatform_whenRepoFoundOnGitHubAndAutoProvisioningOn_successfullyCreatesProject() {
// given
- DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, GITHUB_API_URL, GITHUB_REPO_FULL_NAME);
when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE);
when(gitHubSettings.isProvisioningEnabled()).thenReturn(true);
- AlmSettingDto almSettingDto = mockAlmSettingDto(devOpsProjectDescriptor);
+ AlmSettingDto almSettingDto = mockAlmSettingDto();
mockExistingGitHubRepository(almSettingDto);
ComponentCreationData componentCreationData = mockProjectCreation();
// when
ComponentCreationData actualComponentCreationData = gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, PROJECT_KEY, almSettingDto,
- devOpsProjectDescriptor);
+ DEV_OPS_PROJECT_DESCRIPTOR);
// then
assertThat(actualComponentCreationData).isEqualTo(componentCreationData);
@Test
public void createProjectAndBindToDevOpsPlatform_whenWrongToken_throws() {
- DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, GITHUB_API_URL, GITHUB_REPO_FULL_NAME);
- AlmSettingDto almSettingDto = mockAlmSettingDto(devOpsProjectDescriptor);
+ AlmSettingDto almSettingDto = mockAlmSettingDto();
mockExistingGitHubRepository(almSettingDto);
when(githubApplicationClient.createAppInstallationToken(any(), anyLong())).thenReturn(Optional.empty());
assertThatIllegalStateException().isThrownBy(
- () -> gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, PROJECT_KEY, almSettingDto, devOpsProjectDescriptor))
+ () -> gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, PROJECT_KEY, almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR))
.withMessage("Error while generating token for GitHub Api Url https://api.toto.com (installation id: 534534534543)");
}
+ @Test
+ public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenUserIsCollaboratorWithScan_returnsTrue() {
+ // given
+ AlmSettingDto almSettingDto = mockAlmSettingDto();
+
+ GsonRepositoryCollaborator collaborator1 = mockCollaborator("collaborator1", 1, "role1");
+ GsonRepositoryCollaborator collaborator2 = mockCollaborator("collaborator2", 2, "role2");
+ mockGithubCollaboratorsFromApi(almSettingDto, collaborator1, collaborator2);
+ bindSessionToCollaborator(collaborator2);
+
+ mockPermissionsConversion(collaborator2, "another_perm", UserRole.SCAN);
+
+ // when
+ boolean isGranted = gitHubDevOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR);
+
+ // then
+ assertThat(isGranted).isTrue();
+ }
+
+ @Test
+ public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenUserIsCollaboratorButWithoutScan_returnsFalse() {
+ // given
+ AlmSettingDto almSettingDto = mockAlmSettingDto();
+
+ GsonRepositoryCollaborator collaborator1 = mockCollaborator("collaborator1", 1, "role1");
+ mockGithubCollaboratorsFromApi(almSettingDto, collaborator1);
+ bindSessionToCollaborator(collaborator1);
+
+ mockPermissionsConversion(collaborator1, "admin");
+
+ // when
+ boolean isGranted = gitHubDevOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR);
+
+ // then
+ assertThat(isGranted).isFalse();
+ }
+
+ @Test
+ public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenRepoFoundOnGitHubAndUserIsCollaboratorButWithoutScan_returnsFalse() {
+ // given
+ AlmSettingDto almSettingDto = mockAlmSettingDto();
+
+ GsonRepositoryCollaborator collaborator1 = mockCollaborator("collaborator1", 1, "role1");
+ mockGithubCollaboratorsFromApi(almSettingDto, collaborator1);
+ bindSessionToCollaborator(collaborator1);
+
+ mockPermissionsConversion(collaborator1, "admin");
+
+ // when
+ boolean isGranted = gitHubDevOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR);
+
+ // then
+ assertThat(isGranted).isFalse();
+ }
+
+ @Test
+ public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenRepoFoundOnGitHubAndUserIsNotExternal_returnsFalse() {
+ // given
+ AlmSettingDto almSettingDto = mockAlmSettingDto();
+
+ GsonRepositoryCollaborator collaborator1 = mockCollaborator("collaborator1", 1, "role1");
+ mockGithubCollaboratorsFromApi(almSettingDto, collaborator1);
+ mockPermissionsConversion(collaborator1, "admin");
+
+ // when
+ boolean isGranted = gitHubDevOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR);
+
+ // then
+ assertThat(isGranted).isFalse();
+ }
+
+ private void mockPermissionsConversion(GsonRepositoryCollaborator collaborator, String... sqPermissions) {
+ Set<GithubPermissionsMappingDto> githubPermissionsMappingDtos = mockPermissionsMappingsDtos();
+ when(githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(githubPermissionsMappingDtos, collaborator.roleName(), collaborator.permissions()))
+ .thenReturn(Arrays.stream(sqPermissions).collect(toSet()));
+ }
+
+ private void bindSessionToCollaborator(GsonRepositoryCollaborator collaborator1) {
+ UserSession.ExternalIdentity externalIdentity = new UserSession.ExternalIdentity(String.valueOf(collaborator1.id()), collaborator1.name());
+ when(userSession.getExternalIdentity()).thenReturn(Optional.of(externalIdentity));
+ }
+
+ private static GsonRepositoryCollaborator mockCollaborator(String collaborator1, int id, String role1) {
+ return new GsonRepositoryCollaborator(collaborator1, id, role1,
+ new GsonRepositoryPermissions(false, false, false, false, false));
+ }
+
+ private void mockGithubCollaboratorsFromApi(AlmSettingDto almSettingDto, GsonRepositoryCollaborator... repositoryCollaborators) {
+ GithubAppConfiguration githubAppConfiguration = mockGitHubAppConfiguration(almSettingDto);
+ when(githubApplicationClient.getInstallationId(githubAppConfiguration, GITHUB_REPO_FULL_NAME)).thenReturn(Optional.of(APP_INSTALLATION_ID));
+ AppInstallationToken appInstallationToken = mockAppInstallationToken(githubAppConfiguration, APP_INSTALLATION_ID);
+ when(githubApplicationClient.getRepositoryCollaborators(GITHUB_API_URL, appInstallationToken, ORGANIZATION_NAME, PROJECT_NAME))
+ .thenReturn(Arrays.stream(repositoryCollaborators).collect(toSet()));
+ }
+
+ @Test
+ public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenInstallationIdNotFound_throws() {
+ // given
+ AlmSettingDto almSettingDto = mockAlmSettingDto();
+
+ when(githubApplicationClient.getInstallationId(any(), any())).thenReturn(Optional.empty());
+
+ // when
+ assertThatIllegalStateException()
+ .isThrownBy(() -> gitHubDevOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR))
+ .withMessage("Impossible to find the repository orgname/projectName on GitHub, using the devops config devops-platform-config-1.");
+ }
+
+ @Test
+ public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenUserIsMemberOfAGroupWithScan_returnsTrue() {
+ // given
+ AlmSettingDto almSettingDto = mockAlmSettingDto();
+
+ GsonRepositoryTeam team1 = mockGithubTeam("team1", 1, "role1");
+ GsonRepositoryTeam team2 = mockGithubTeam("team2", 2, "role2");
+ mockTeamsFromApi(almSettingDto, team1, team2);
+ bindGroupsToUser(team1.name(), team2.name());
+
+ mockPermissionsConversion(team1, "another_perm", UserRole.SCAN);
+
+ // when
+ boolean isGranted = gitHubDevOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR);
+
+ // then
+ assertThat(isGranted).isTrue();
+ }
+
+ @Test
+ public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenUserIsMemberOfAGroupWithoutScanPermission_returnsFalse() {
+ // given
+ AlmSettingDto almSettingDto = mockAlmSettingDto();
+
+ GsonRepositoryTeam team1 = mockGithubTeam("team1", 1, "role1");
+ mockTeamsFromApi(almSettingDto, team1);
+ bindGroupsToUser(team1.name(), "another_local_team");
+
+ mockPermissionsConversion(team1, "another_perm", "admin");
+
+ // when
+ boolean isGranted = gitHubDevOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR);
+
+ // then
+ assertThat(isGranted).isFalse();
+ }
+
+ @Test
+ public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenUserIsNotMemberOfAGroupWithScanPermission_returnsFalse() {
+ // given
+ AlmSettingDto almSettingDto = mockAlmSettingDto();
+
+ GsonRepositoryTeam team1 = mockGithubTeam("team1", 1, "role1");
+ mockTeamsFromApi(almSettingDto, team1);
+ bindGroupsToUser("another_local_team");
+
+ mockPermissionsConversion(team1, "another_perm", UserRole.SCAN);
+
+ // when
+ boolean isGranted = gitHubDevOpsPlatformService.isScanAllowedUsingPermissionsFromDevopsPlatform(almSettingDto, DEV_OPS_PROJECT_DESCRIPTOR);
+
+ // then
+ assertThat(isGranted).isFalse();
+ }
+
+ private void bindGroupsToUser(String... groupNames) {
+ Set<GroupDto> groupDtos = Arrays.stream(groupNames)
+ .map(groupName -> new GroupDto().setName(ORGANIZATION_NAME + "/" + groupName).setUuid("uuid_" + groupName))
+ .collect(toSet());
+ when(userSession.getGroups()).thenReturn(groupDtos);
+ }
+
+ private static GsonRepositoryTeam mockGithubTeam(String name, int id, String role) {
+ return new GsonRepositoryTeam(name, id, name + "slug", role, new GsonRepositoryPermissions(false, false, false, false, false));
+ }
+
+ private void mockTeamsFromApi(AlmSettingDto almSettingDto, GsonRepositoryTeam... repositoryTeams) {
+ GithubAppConfiguration githubAppConfiguration = mockGitHubAppConfiguration(almSettingDto);
+ when(githubApplicationClient.getInstallationId(githubAppConfiguration, GITHUB_REPO_FULL_NAME)).thenReturn(Optional.of(APP_INSTALLATION_ID));
+ AppInstallationToken appInstallationToken = mockAppInstallationToken(githubAppConfiguration, APP_INSTALLATION_ID);
+ when(githubApplicationClient.getRepositoryTeams(GITHUB_API_URL, appInstallationToken, ORGANIZATION_NAME, PROJECT_NAME))
+ .thenReturn(Arrays.stream(repositoryTeams).collect(toSet()));
+ }
+
+ private void mockPermissionsConversion(GsonRepositoryTeam team, String... sqPermissions) {
+ Set<GithubPermissionsMappingDto> githubPermissionsMappingDtos = mockPermissionsMappingsDtos();
+ when(githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(githubPermissionsMappingDtos, team.permission(), team.permissions()))
+ .thenReturn(Arrays.stream(sqPermissions).collect(toSet()));
+ }
+
+ private Set<GithubPermissionsMappingDto> mockPermissionsMappingsDtos() {
+ Set<GithubPermissionsMappingDto> githubPermissionsMappingDtos = Set.of(mock(GithubPermissionsMappingDto.class));
+ when(dbClient.githubPermissionsMappingDao().findAll(any())).thenReturn(githubPermissionsMappingDtos);
+ return githubPermissionsMappingDtos;
+ }
+
private void mockExistingGitHubRepository(AlmSettingDto almSettingDto) {
GithubAppConfiguration githubAppConfiguration = mockGitHubAppConfiguration(almSettingDto);
when(githubApplicationClient.getInstallationId(githubAppConfiguration, GITHUB_REPO_FULL_NAME)).thenReturn(Optional.of(APP_INSTALLATION_ID));
return appInstallationToken;
}
- private static AlmSettingDto mockAlmSettingDto(DevOpsProjectDescriptor devOpsProjectDescriptor) {
+ private static AlmSettingDto mockAlmSettingDto() {
AlmSettingDto almSettingDto = mock();
when(almSettingDto.getUuid()).thenReturn("almsetting-uuid-1");
when(almSettingDto.getKey()).thenReturn("devops-platform-config-1");
import org.sonar.auth.bitbucket.BitbucketModule;
import org.sonar.auth.github.GitHubModule;
import org.sonar.auth.github.GitHubSettings;
+import org.sonar.auth.github.GithubPermissionConverter;
import org.sonar.auth.gitlab.GitLabModule;
import org.sonar.auth.ldap.LdapModule;
import org.sonar.auth.saml.SamlModule;
GithubProvisioningConfigValidator.class,
GithubProvisioningWs.class,
GitHubDevOpsPlatformService.class,
+ GithubPermissionConverter.class,
BitbucketCloudRestClientConfiguration.class,
BitbucketServerRestClient.class,
GitlabHttpClient.class,