3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.server.almsettings.ws;
22 import java.util.Arrays;
23 import java.util.Optional;
25 import org.junit.Before;
26 import org.junit.Test;
27 import org.junit.runner.RunWith;
28 import org.mockito.Answers;
29 import org.mockito.ArgumentCaptor;
30 import org.mockito.Captor;
31 import org.mockito.Mock;
32 import org.mockito.junit.MockitoJUnitRunner;
33 import org.sonar.alm.client.github.GithubApplicationClient;
34 import org.sonar.alm.client.github.api.GsonRepositoryCollaborator;
35 import org.sonar.alm.client.github.api.GsonRepositoryTeam;
36 import org.sonar.alm.client.github.security.AccessToken;
37 import org.sonar.api.resources.Qualifiers;
38 import org.sonar.api.web.UserRole;
39 import org.sonar.auth.github.GithubPermissionConverter;
40 import org.sonar.auth.github.GsonRepositoryPermissions;
41 import org.sonar.db.DbClient;
42 import org.sonar.db.alm.setting.ALM;
43 import org.sonar.db.alm.setting.AlmSettingDto;
44 import org.sonar.db.alm.setting.ProjectAlmSettingDao;
45 import org.sonar.db.alm.setting.ProjectAlmSettingDto;
46 import org.sonar.db.project.CreationMethod;
47 import org.sonar.db.project.ProjectDto;
48 import org.sonar.db.provisioning.GithubPermissionsMappingDto;
49 import org.sonar.db.user.GroupDto;
50 import org.sonar.server.almintegration.ws.ProjectKeyGenerator;
51 import org.sonar.server.component.ComponentCreationData;
52 import org.sonar.server.component.ComponentCreationParameters;
53 import org.sonar.server.component.ComponentUpdater;
54 import org.sonar.server.component.NewComponent;
55 import org.sonar.server.user.UserSession;
57 import static java.util.Objects.requireNonNull;
58 import static java.util.stream.Collectors.toSet;
59 import static org.assertj.core.api.Assertions.assertThat;
60 import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
61 import static org.mockito.ArgumentMatchers.any;
62 import static org.mockito.ArgumentMatchers.eq;
63 import static org.mockito.Mockito.mock;
64 import static org.mockito.Mockito.verify;
65 import static org.mockito.Mockito.when;
66 import static org.sonar.db.project.CreationMethod.ALM_IMPORT_API;
67 import static org.sonar.db.project.CreationMethod.SCANNER_API_DEVOPS_AUTO_CONFIG;
69 @RunWith(MockitoJUnitRunner.class)
70 public class GithubProjectCreatorTest {
72 private static final String ORGANIZATION_NAME = "orga2";
73 private static final String REPOSITORY_NAME = "repo1";
75 private static final String MAIN_BRANCH_NAME = "defaultBranch";
76 private static final DevOpsProjectDescriptor DEVOPS_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITHUB, "http://api.com", ORGANIZATION_NAME + "/" + REPOSITORY_NAME);
77 private static final String ALM_SETTING_KEY = "github_config_1";
78 private static final String USER_LOGIN = "userLogin";
79 private static final String USER_UUID = "userUuid";
80 @Mock(answer = Answers.RETURNS_DEEP_STUBS)
81 private DbClient dbClient;
83 private GithubApplicationClient githubApplicationClient;
85 private GithubPermissionConverter githubPermissionConverter;
87 private ProjectKeyGenerator projectKeyGenerator;
89 private ComponentUpdater componentUpdater;
91 private GithubProjectCreationParameters githubProjectCreationParameters;
93 private AccessToken appInstallationToken;
95 private UserSession userSession;
97 private AlmSettingDto almSettingDto;
99 private GithubProjectCreator githubProjectCreator;
102 ArgumentCaptor<ComponentCreationParameters> componentCreationParametersCaptor;
104 ArgumentCaptor<ProjectAlmSettingDto> projectAlmSettingDtoCaptor;
107 public void setup() {
108 when(userSession.getLogin()).thenReturn(USER_LOGIN);
109 when(userSession.getUuid()).thenReturn(USER_UUID);
111 when(almSettingDto.getUrl()).thenReturn(DEVOPS_PROJECT_DESCRIPTOR.url());
112 when(almSettingDto.getKey()).thenReturn(ALM_SETTING_KEY);
114 when(githubProjectCreationParameters.devOpsProjectDescriptor()).thenReturn(DEVOPS_PROJECT_DESCRIPTOR);
115 when(githubProjectCreationParameters.userSession()).thenReturn(userSession);
116 when(githubProjectCreationParameters.appInstallationToken()).thenReturn(appInstallationToken);
117 when(githubProjectCreationParameters.almSettingDto()).thenReturn(almSettingDto);
119 githubProjectCreator = new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, componentUpdater,
120 githubProjectCreationParameters);
122 /* when(githubProjectCreationParameters.almSettingDto()).thenReturn();
123 when(githubProjectCreationParameters.almSettingDto()).thenReturn();
124 when(githubProjectCreationParameters.almSettingDto()).thenReturn();
125 when(githubProjectCreationParameters.almSettingDto()).thenReturn();*/
129 public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenUserIsNotAGitHubUser_returnsFalse() {
130 assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isFalse();
134 public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenCollaboratorHasDirectAccessButNoScanPermissions_returnsFalse() {
135 GsonRepositoryCollaborator collaborator1 = mockCollaborator("collaborator1", 1, "role1", "read", "admin");
136 mockGithubCollaboratorsFromApi(collaborator1);
137 bindSessionToCollaborator(collaborator1);
139 assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isFalse();
143 public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenCollaboratorHasDirectAccess_returnsTrue() {
144 GsonRepositoryCollaborator collaborator1 = mockCollaborator("collaborator1", 1, "role1", "read", "admin");
145 GsonRepositoryCollaborator collaborator2 = mockCollaborator("collaborator2", 2, "role2", "read", "scan");
146 mockGithubCollaboratorsFromApi(collaborator1, collaborator2);
147 bindSessionToCollaborator(collaborator2);
149 assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isTrue();
153 public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenAccessViaTeamButNoScanPermissions_returnsFalse() {
154 GsonRepositoryTeam team2 = mockGithubTeam("team2", 2, "role2", "another_perm", UserRole.ADMIN);
155 mockTeamsFromApi(team2);
156 bindGroupsToUser(team2.name());
158 assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isFalse();
162 public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenAccessViaTeam_returnsTrue() {
163 GsonRepositoryTeam team1 = mockGithubTeam("team1", 1, "role1", "read", "another_perm");
164 GsonRepositoryTeam team2 = mockGithubTeam("team2", 2, "role2", "another_perm", UserRole.SCAN);
165 mockTeamsFromApi(team1, team2);
166 bindGroupsToUser(team1.name(), team2.name());
168 assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isTrue();
172 public void isScanAllowedUsingPermissionsFromDevopsPlatform_whenAccessViaTeamButUserNotInTeam_returnsFalse() {
173 GsonRepositoryTeam team1 = mockGithubTeam("team1", 1, "role1", "read", "another_perm");
174 GsonRepositoryTeam team2 = mockGithubTeam("team2", 2, "role2", "another_perm", UserRole.SCAN);
175 mockTeamsFromApi(team1, team2);
176 bindGroupsToUser(team1.name());
178 assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isFalse();
181 private void bindSessionToCollaborator(GsonRepositoryCollaborator collaborator1) {
182 UserSession.ExternalIdentity externalIdentity = new UserSession.ExternalIdentity(String.valueOf(collaborator1.id()), collaborator1.name());
183 when(userSession.getExternalIdentity()).thenReturn(Optional.of(externalIdentity));
186 private GsonRepositoryCollaborator mockCollaborator(String collaboratorLogin, int id, String role1, String... sqPermissions) {
187 GsonRepositoryCollaborator collaborator = new GsonRepositoryCollaborator(collaboratorLogin, id, role1,
188 new GsonRepositoryPermissions(false, false, false, false, false));
189 mockPermissionsConversion(collaborator, sqPermissions);
193 private void mockGithubCollaboratorsFromApi(GsonRepositoryCollaborator... repositoryCollaborators) {
194 Set<GsonRepositoryCollaborator> collaborators = Arrays.stream(repositoryCollaborators).collect(toSet());
195 when(githubApplicationClient.getRepositoryCollaborators(DEVOPS_PROJECT_DESCRIPTOR.url(), appInstallationToken, ORGANIZATION_NAME, REPOSITORY_NAME)).thenReturn(collaborators);
198 private GsonRepositoryTeam mockGithubTeam(String name, int id, String role, String... sqPermissions) {
199 GsonRepositoryTeam gsonRepositoryTeam = new GsonRepositoryTeam(name, id, name + "slug", role, new GsonRepositoryPermissions(false, false, false, false, false));
200 mockPermissionsConversion(gsonRepositoryTeam, sqPermissions);
201 return gsonRepositoryTeam;
204 private void mockTeamsFromApi(GsonRepositoryTeam... repositoryTeams) {
205 when(githubApplicationClient.getRepositoryTeams(DEVOPS_PROJECT_DESCRIPTOR.url(), appInstallationToken, ORGANIZATION_NAME, REPOSITORY_NAME))
206 .thenReturn(Arrays.stream(repositoryTeams).collect(toSet()));
209 private void mockPermissionsConversion(GsonRepositoryCollaborator collaborator, String... sqPermissions) {
210 Set<GithubPermissionsMappingDto> githubPermissionsMappingDtos = mockPermissionsMappingsDtos();
211 when(githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(githubPermissionsMappingDtos, collaborator.roleName(), collaborator.permissions()))
212 .thenReturn(Arrays.stream(sqPermissions).collect(toSet()));
215 private void mockPermissionsConversion(GsonRepositoryTeam team, String... sqPermissions) {
216 Set<GithubPermissionsMappingDto> githubPermissionsMappingDtos = mockPermissionsMappingsDtos();
217 when(githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(githubPermissionsMappingDtos, team.permission(), team.permissions()))
218 .thenReturn(Arrays.stream(sqPermissions).collect(toSet()));
221 private Set<GithubPermissionsMappingDto> mockPermissionsMappingsDtos() {
222 Set<GithubPermissionsMappingDto> githubPermissionsMappingDtos = Set.of(mock(GithubPermissionsMappingDto.class));
223 when(dbClient.githubPermissionsMappingDao().findAll(any())).thenReturn(githubPermissionsMappingDtos);
224 return githubPermissionsMappingDtos;
227 private void bindGroupsToUser(String... groupNames) {
228 Set<GroupDto> groupDtos = Arrays.stream(groupNames)
229 .map(groupName -> new GroupDto().setName(ORGANIZATION_NAME + "/" + groupName).setUuid("uuid_" + groupName))
231 when(userSession.getGroups()).thenReturn(groupDtos);
235 public void createProjectAndBindToDevOpsPlatform_whenRepoNotFound_throws() {
236 assertThatIllegalStateException().isThrownBy(
237 () -> githubProjectCreator.createProjectAndBindToDevOpsPlatform(mock(), SCANNER_API_DEVOPS_AUTO_CONFIG, null))
238 .withMessage("Impossible to find the repository 'orga2/repo1' on GitHub, using the devops config " + ALM_SETTING_KEY);
242 public void createProjectAndBindToDevOpsPlatformFromScanner_whenRepoFoundOnGitHub_successfullyCreatesProject() {
244 mockGitHubRepository();
246 ComponentCreationData componentCreationData = mockProjectCreation("generated_orga2/repo1");
247 ProjectAlmSettingDao projectAlmSettingDao = mock();
248 when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao);
251 ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true),
252 SCANNER_API_DEVOPS_AUTO_CONFIG, null);
255 assertThat(actualComponentCreationData).isEqualTo(componentCreationData);
257 ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue();
258 assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters, "generated_orga2/repo1", SCANNER_API_DEVOPS_AUTO_CONFIG);
259 assertThat(componentCreationParameters.isManaged()).isFalse();
260 assertThat(componentCreationParameters.newComponent().isPrivate()).isFalse();
262 verify(projectAlmSettingDao).insertOrUpdate(any(), projectAlmSettingDtoCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq("generated_orga2/repo1"));
263 ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue();
264 assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto);
268 public void createProjectAndBindToDevOpsPlatformFromApi_whenRepoFoundOnGitHub_successfullyCreatesProject() {
270 String projectKey = "customProjectKey";
271 mockGitHubRepository();
273 ComponentCreationData componentCreationData = mockProjectCreation(projectKey);
274 ProjectAlmSettingDao projectAlmSettingDao = mock();
275 when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao);
278 ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), ALM_IMPORT_API, projectKey);
281 assertThat(actualComponentCreationData).isEqualTo(componentCreationData);
283 ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue();
284 assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters, projectKey, ALM_IMPORT_API);
285 assertThat(componentCreationParameters.isManaged()).isFalse();
286 assertThat(componentCreationParameters.newComponent().isPrivate()).isFalse();
288 verify(projectAlmSettingDao).insertOrUpdate(any(), projectAlmSettingDtoCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq(projectKey));
289 ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue();
290 assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto);
293 public void createProjectAndBindToDevOpsPlatformFromApi_whenRepoFoundOnGitHubAutoProvisioningOnAndRepoPrivate_successfullyCreatesProject() {
295 when(githubProjectCreationParameters.projectsArePrivateByDefault()).thenReturn(true);
296 when(githubProjectCreationParameters.isProvisioningEnabled()).thenReturn(true);
298 String projectKey = "customProjectKey";
299 mockGitHubRepository();
301 ComponentCreationData componentCreationData = mockProjectCreation(projectKey);
302 ProjectAlmSettingDao projectAlmSettingDao = mock();
303 when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao);
306 ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), ALM_IMPORT_API, projectKey);
309 assertThat(actualComponentCreationData).isEqualTo(componentCreationData);
311 ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue();
312 assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters, projectKey, ALM_IMPORT_API);
313 assertThat(componentCreationParameters.isManaged()).isTrue();
314 assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue();
316 verify(projectAlmSettingDao).insertOrUpdate(any(), projectAlmSettingDtoCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq(projectKey));
317 ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue();
318 assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto);
321 private void mockGitHubRepository() {
322 GithubApplicationClient.Repository repository = mock();
323 when(repository.getDefaultBranch()).thenReturn(MAIN_BRANCH_NAME);
324 when(repository.getName()).thenReturn(REPOSITORY_NAME);
325 when(repository.getFullName()).thenReturn(DEVOPS_PROJECT_DESCRIPTOR.projectIdentifier());
326 when(githubApplicationClient.getRepository(DEVOPS_PROJECT_DESCRIPTOR.url(), appInstallationToken, DEVOPS_PROJECT_DESCRIPTOR.projectIdentifier())).thenReturn(
327 Optional.of(repository));
328 when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn("generated_" + DEVOPS_PROJECT_DESCRIPTOR.projectIdentifier());
331 private ComponentCreationData mockProjectCreation(String projectKey) {
332 ComponentCreationData componentCreationData = mock();
333 ProjectDto projectDto = mockProjectDto(projectKey);
334 when(componentCreationData.projectDto()).thenReturn(projectDto);
335 when(componentUpdater.createWithoutCommit(any(), componentCreationParametersCaptor.capture())).thenReturn(componentCreationData);
336 return componentCreationData;
339 private static ProjectDto mockProjectDto(String projectKey) {
340 ProjectDto projectDto = mock();
341 when(projectDto.getName()).thenReturn(REPOSITORY_NAME);
342 when(projectDto.getKey()).thenReturn(projectKey);
343 when(projectDto.getUuid()).thenReturn("project-uuid-1");
347 private static void assertComponentCreationParametersContainsCorrectInformation(ComponentCreationParameters componentCreationParameters, String expectedKey,
348 CreationMethod expectedCreationMethod) {
349 assertThat(componentCreationParameters.creationMethod()).isEqualTo(expectedCreationMethod);
350 assertThat(componentCreationParameters.mainBranchName()).isEqualTo(MAIN_BRANCH_NAME);
351 assertThat(componentCreationParameters.userLogin()).isEqualTo(USER_LOGIN);
352 assertThat(componentCreationParameters.userUuid()).isEqualTo(USER_UUID);
354 NewComponent newComponent = componentCreationParameters.newComponent();
355 assertThat(newComponent.isProject()).isTrue();
356 assertThat(newComponent.qualifier()).isEqualTo(Qualifiers.PROJECT);
357 assertThat(newComponent.key()).isEqualTo(expectedKey);
358 assertThat(newComponent.name()).isEqualTo(REPOSITORY_NAME);
361 private static void assertAlmSettingsDtoContainsCorrectInformation(AlmSettingDto almSettingDto, ProjectDto projectDto, ProjectAlmSettingDto projectAlmSettingDto) {
362 assertThat(projectAlmSettingDto.getAlmRepo()).isEqualTo(DEVOPS_PROJECT_DESCRIPTOR.projectIdentifier());
363 assertThat(projectAlmSettingDto.getAlmSlug()).isNull();
364 assertThat(projectAlmSettingDto.getAlmSettingUuid()).isEqualTo(almSettingDto.getUuid());
365 assertThat(projectAlmSettingDto.getProjectUuid()).isEqualTo(projectDto.getUuid());
366 assertThat(projectAlmSettingDto.getMonorepo()).isFalse();
367 assertThat(projectAlmSettingDto.getSummaryCommentEnabled()).isTrue();