From: Wojtek Wajerowicz <115081248+wojciech-wajerowicz-sonarsource@users.noreply.github.com> Date: Tue, 19 Mar 2024 13:15:49 +0000 (+0100) Subject: SONAR-21819 Extract logic reusable between legacy and v2 endpoints. X-Git-Tag: 10.5.0.89998~76 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=412f42cb112802614e541ca186d3e35006f28be9;p=sonarqube.git SONAR-21819 Extract logic reusable between legacy and v2 endpoints. --- diff --git a/server/sonar-webserver-common/build.gradle b/server/sonar-webserver-common/build.gradle index d12d353e29b..89ce2803657 100644 --- a/server/sonar-webserver-common/build.gradle +++ b/server/sonar-webserver-common/build.gradle @@ -26,11 +26,15 @@ dependencies { testImplementation 'org.assertj:assertj-core' testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.mockito:mockito-core' + testImplementation 'org.mockito:mockito-junit-jupiter' testImplementation project(':sonar-testing-harness') testImplementation testFixtures(project(':server:sonar-db-dao')) testImplementation testFixtures(project(':server:sonar-server-common')) testImplementation testFixtures(project(':server:sonar-webserver-api')) + testImplementation testFixtures(project(':server:sonar-webserver-auth')) + testImplementation testFixtures(project(':server:sonar-webserver-es')) + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' diff --git a/server/sonar-webserver-common/src/it/java/org/sonar/server/common/component/ComponentUpdaterIT.java b/server/sonar-webserver-common/src/it/java/org/sonar/server/common/component/ComponentUpdaterIT.java new file mode 100644 index 00000000000..30a70a126ef --- /dev/null +++ b/server/sonar-webserver-common/src/it/java/org/sonar/server/common/component/ComponentUpdaterIT.java @@ -0,0 +1,545 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.component; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import org.apache.commons.lang3.StringUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.Scopes; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +import org.sonar.core.util.SequenceUuidFactory; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.audit.AuditPersister; +import org.sonar.db.component.BranchDto; +import org.sonar.db.component.BranchType; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ResourceTypesRule; +import org.sonar.db.project.CreationMethod; +import org.sonar.db.project.ProjectDto; +import org.sonar.db.user.UserDto; +import org.sonar.server.common.permission.GroupPermissionChanger; +import org.sonar.server.common.permission.PermissionTemplateService; +import org.sonar.server.common.permission.PermissionUpdater; +import org.sonar.server.common.permission.UserPermissionChange; +import org.sonar.server.common.permission.UserPermissionChanger; +import org.sonar.server.component.ComponentCreationData; +import org.sonar.server.es.EsTester; +import org.sonar.server.es.Indexers; +import org.sonar.server.es.IndexersImpl; +import org.sonar.server.es.TestIndexers; +import org.sonar.server.exceptions.BadRequestException; +import org.sonar.server.favorite.FavoriteUpdater; +import org.sonar.server.l18n.I18nRule; +import org.sonar.server.permission.PermissionService; +import org.sonar.server.permission.PermissionServiceImpl; +import org.sonar.server.permission.index.FooIndexDefinition; +import org.sonar.server.permission.index.PermissionIndexer; +import org.sonar.server.project.DefaultBranchNameResolver; + +import static java.util.stream.IntStream.rangeClosed; +import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.sonar.api.resources.Qualifiers.APP; +import static org.sonar.api.resources.Qualifiers.PROJECT; +import static org.sonar.api.resources.Qualifiers.VIEW; +import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME; + +public class ComponentUpdaterIT { + + private static final String DEFAULT_PROJECT_KEY = "project-key"; + private static final String DEFAULT_PROJECT_NAME = "project-name"; + private static final NewComponent DEFAULT_COMPONENT = NewComponent.newComponentBuilder() + .setKey(DEFAULT_PROJECT_KEY) + .setName(DEFAULT_PROJECT_NAME) + .build(); + private static final NewComponent PRIVATE_COMPONENT = NewComponent.newComponentBuilder() + .setKey(DEFAULT_PROJECT_KEY) + .setName(DEFAULT_PROJECT_NAME) + .setPrivate(true) + .build(); + private static final String DEFAULT_USER_UUID = "user-uuid"; + public static final String DEFAULT_USER_LOGIN = "user-login"; + + private final System2 system2 = System2.INSTANCE; + + private final AuditPersister auditPersister = mock(); + + @Rule + public final DbTester db = DbTester.create(system2, auditPersister); + @Rule + public final I18nRule i18n = new I18nRule().put("qualifier.TRK", "Project"); + + private final TestIndexers projectIndexers = new TestIndexers(); + private final PermissionTemplateService permissionTemplateService = mock(PermissionTemplateService.class); + private final DefaultBranchNameResolver defaultBranchNameResolver = mock(DefaultBranchNameResolver.class); + public EsTester es = EsTester.createCustom(new FooIndexDefinition()); + private final PermissionUpdater userPermissionUpdater = new PermissionUpdater( + new IndexersImpl(new PermissionIndexer(db.getDbClient(), es.client())), + Set.of(new UserPermissionChanger(db.getDbClient(), new SequenceUuidFactory()), + new GroupPermissionChanger(db.getDbClient(), new SequenceUuidFactory()))); + private final PermissionService permissionService = new PermissionServiceImpl(new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT)); + + private final ComponentUpdater underTest = new ComponentUpdater(db.getDbClient(), i18n, system2, + permissionTemplateService, + new FavoriteUpdater(db.getDbClient()), + projectIndexers, new SequenceUuidFactory(), defaultBranchNameResolver, userPermissionUpdater, permissionService); + + @Before + public void before() { + when(defaultBranchNameResolver.getEffectiveMainBranchName()).thenReturn(DEFAULT_MAIN_BRANCH_NAME); + } + + @Test + public void persist_and_index_when_creating_project() { + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(PRIVATE_COMPONENT) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + ComponentCreationData returned = underTest.create(db.getSession(), creationParameters); + + ComponentDto loaded = db.getDbClient().componentDao().selectOrFailByUuid(db.getSession(), returned.mainBranchComponent().uuid()); + assertThat(loaded.getKey()).isEqualTo(DEFAULT_PROJECT_KEY); + assertThat(loaded.name()).isEqualTo(DEFAULT_PROJECT_NAME); + assertThat(loaded.longName()).isEqualTo(DEFAULT_PROJECT_NAME); + assertThat(loaded.qualifier()).isEqualTo(Qualifiers.PROJECT); + assertThat(loaded.scope()).isEqualTo(Scopes.PROJECT); + assertThat(loaded.uuid()).isNotNull(); + assertThat(loaded.branchUuid()).isEqualTo(loaded.uuid()); + assertThat(loaded.isPrivate()).isEqualTo(PRIVATE_COMPONENT.isPrivate()); + assertThat(loaded.getCreatedAt()).isNotNull(); + assertThat(db.getDbClient().componentDao().selectByKey(db.getSession(), DEFAULT_PROJECT_KEY)).isPresent(); + + assertThat(projectIndexers.hasBeenCalledForEntity(returned.projectDto().getUuid(), Indexers.EntityEvent.CREATION)).isTrue(); + + Optional branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), returned.mainBranchComponent().uuid()); + assertThat(branch).isPresent(); + assertThat(branch.get().getKey()).isEqualTo(DEFAULT_MAIN_BRANCH_NAME); + assertThat(branch.get().getMergeBranchUuid()).isNull(); + assertThat(branch.get().getBranchType()).isEqualTo(BranchType.BRANCH); + assertThat(branch.get().getUuid()).isEqualTo(returned.mainBranchComponent().uuid()); + assertThat(branch.get().getProjectUuid()).isEqualTo(returned.projectDto().getUuid()); + } + + @Test + public void create_project_with_main_branch_global_property() { + when(defaultBranchNameResolver.getEffectiveMainBranchName()).thenReturn("main-branch-global"); + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(PRIVATE_COMPONENT) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + + ComponentDto returned = underTest.create(db.getSession(), creationParameters).mainBranchComponent(); + + Optional branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), returned.branchUuid()); + assertThat(branch).get().extracting(BranchDto::getBranchKey).isEqualTo("main-branch-global"); + } + + @Test + public void persist_private_flag_true_when_creating_project() { + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(PRIVATE_COMPONENT) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + ComponentDto returned = underTest.create(db.getSession(), creationParameters).mainBranchComponent(); + ComponentDto loaded = db.getDbClient().componentDao().selectOrFailByUuid(db.getSession(), returned.uuid()); + assertThat(loaded.isPrivate()).isEqualTo(PRIVATE_COMPONENT.isPrivate()); + } + + @Test + public void persist_private_flag_false_when_creating_project() { + NewComponent project = NewComponent.newComponentBuilder() + .setKey(DEFAULT_PROJECT_KEY) + .setName(DEFAULT_PROJECT_NAME) + .setPrivate(false) + .build(); + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(project) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + ComponentDto returned = underTest.create(db.getSession(), creationParameters).mainBranchComponent(); + ComponentDto loaded = db.getDbClient().componentDao().selectOrFailByUuid(db.getSession(), returned.uuid()); + assertThat(loaded.isPrivate()).isEqualTo(project.isPrivate()); + } + + @Test + public void create_view() { + NewComponent view = NewComponent.newComponentBuilder() + .setKey("view-key") + .setName("view-name") + .setQualifier(VIEW) + .build(); + + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(view) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + ComponentDto returned = underTest.create(db.getSession(), creationParameters).mainBranchComponent(); + + ComponentDto loaded = db.getDbClient().componentDao().selectOrFailByUuid(db.getSession(), returned.uuid()); + assertThat(loaded.getKey()).isEqualTo("view-key"); + assertThat(loaded.name()).isEqualTo("view-name"); + assertThat(loaded.qualifier()).isEqualTo("VW"); + assertThat(projectIndexers.hasBeenCalledForEntity(loaded.uuid(), Indexers.EntityEvent.CREATION)).isTrue(); + Optional branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), returned.uuid()); + assertThat(branch).isNotPresent(); + } + + @Test + public void create_application() { + NewComponent application = NewComponent.newComponentBuilder() + .setKey("app-key") + .setName("app-name") + .setQualifier(APP) + .build(); + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(application) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + ComponentCreationData returned = underTest.create(db.getSession(), creationParameters); + + ProjectDto loaded = db.getDbClient().projectDao().selectByUuid(db.getSession(), returned.projectDto().getUuid()).get(); + assertThat(loaded.getKey()).isEqualTo("app-key"); + assertThat(loaded.getName()).isEqualTo("app-name"); + assertThat(loaded.getQualifier()).isEqualTo("APP"); + assertThat(projectIndexers.hasBeenCalledForEntity(loaded.getUuid(), Indexers.EntityEvent.CREATION)).isTrue(); + Optional branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), returned.mainBranchComponent().uuid()); + assertThat(branch).isPresent(); + assertThat(branch.get().getKey()).isEqualTo(DEFAULT_MAIN_BRANCH_NAME); + assertThat(branch.get().getMergeBranchUuid()).isNull(); + assertThat(branch.get().getBranchType()).isEqualTo(BranchType.BRANCH); + assertThat(branch.get().getUuid()).isEqualTo(returned.mainBranchComponent().uuid()); + assertThat(branch.get().getProjectUuid()).isEqualTo(returned.projectDto().getUuid()); + } + + @Test + public void apply_default_permission_template() { + ComponentCreationParameters componentCreationParameters = ComponentCreationParameters.builder() + .newComponent(DEFAULT_COMPONENT) + .userLogin(DEFAULT_USER_LOGIN) + .userUuid(DEFAULT_USER_UUID) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + + ProjectDto dto = underTest.create(db.getSession(), componentCreationParameters).projectDto(); + + verify(permissionTemplateService).applyDefaultToNewComponent(db.getSession(), dto, DEFAULT_USER_UUID); + } + + @Test + public void add_project_to_user_favorites_if_project_creator_is_defined_in_permission_template() { + UserDto userDto = db.users().insertUser(); + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(DEFAULT_COMPONENT) + .userLogin(userDto.getLogin()) + .userUuid(userDto.getUuid()) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + + when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), any(ProjectDto.class))) + .thenReturn(true); + + ProjectDto dto = underTest.create(db.getSession(), creationParameters).projectDto(); + + assertThat(db.favorites().hasFavorite(dto, userDto.getUuid())).isTrue(); + } + + @Test + public void do_not_add_project_to_user_favorites_if_project_creator_is_defined_in_permission_template_and_already_100_favorites() { + UserDto user = db.users().insertUser(); + rangeClosed(1, 100).forEach(i -> db.favorites().add(db.components().insertPrivateProject().getProjectDto(), user.getUuid(), user.getLogin())); + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(DEFAULT_COMPONENT) + .userLogin(user.getLogin()) + .userUuid(user.getUuid()) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + + when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(eq(db.getSession()), any(ProjectDto.class))) + .thenReturn(true); + + ProjectDto dto = underTest.create(db.getSession(), creationParameters).projectDto(); + + assertThat(db.favorites().hasFavorite(dto, user.getUuid())).isFalse(); + } + + @Test + public void does_not_add_project_to_favorite_when_anonymously_created() { + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(DEFAULT_COMPONENT) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + ProjectDto projectDto = underTest.create(db.getSession(), creationParameters).projectDto(); + + assertThat(db.favorites().hasNoFavorite(projectDto)).isTrue(); + } + + @Test + public void fail_when_project_key_already_exists() { + ComponentDto existing = db.components().insertPrivateProject().getMainBranchComponent(); + DbSession session = db.getSession(); + + NewComponent project = NewComponent.newComponentBuilder() + .setKey(existing.getKey()) + .setName(DEFAULT_PROJECT_NAME) + .build(); + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(project) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + + assertThatThrownBy(() -> underTest.create(session, creationParameters)) + .isInstanceOf(BadRequestException.class) + .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s\"", existing.getKey(), existing.getKey()); + } + + @Test + public void fail_when_key_has_bad_format() { + DbSession session = db.getSession(); + NewComponent project = NewComponent.newComponentBuilder() + .setKey("1234") + .setName(DEFAULT_PROJECT_NAME) + .build(); + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(project) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + + assertThatThrownBy(() -> underTest.create(session, creationParameters)) + .isInstanceOf(BadRequestException.class) + .hasMessageContaining("Malformed key for Project: '1234'"); + } + + @Test + public void fail_when_key_contains_percent_character() { + DbSession session = db.getSession(); + NewComponent project = NewComponent.newComponentBuilder() + .setKey("roject%Key") + .setName(DEFAULT_PROJECT_NAME) + .build(); + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(project) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + + assertThatThrownBy(() -> underTest.create(session, creationParameters)) + .isInstanceOf(BadRequestException.class) + .hasMessageContaining("Malformed key for Project: 'roject%Key'"); + } + + @Test + public void create_shouldFail_whenCreatingProjectWithExistingKeyButDifferentCase() { + createComponent_shouldFail_whenCreatingComponentWithExistingKeyButDifferentCase(PROJECT); + } + + @Test + public void create_shouldFail_whenCreatingPortfolioWithExistingKeyButDifferentCase() { + createComponent_shouldFail_whenCreatingComponentWithExistingKeyButDifferentCase(VIEW); + } + + @Test + public void create_shouldFail_whenCreatingApplicationWithExistingKeyButDifferentCase() { + createComponent_shouldFail_whenCreatingComponentWithExistingKeyButDifferentCase(APP); + } + + private void createComponent_shouldFail_whenCreatingComponentWithExistingKeyButDifferentCase(String qualifier) { + String existingKey = randomAlphabetic(5).toUpperCase(); + db.components().insertPrivateProject(component -> component.setKey(existingKey)); + String newKey = existingKey.toLowerCase(); + + NewComponent project = NewComponent.newComponentBuilder() + .setKey(newKey) + .setName(DEFAULT_PROJECT_NAME) + .setQualifier(qualifier) + .build(); + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(project) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + + DbSession dbSession = db.getSession(); + assertThatThrownBy(() -> underTest.create(dbSession, creationParameters)) + .isInstanceOf(BadRequestException.class) + .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s\"", newKey, existingKey); + } + + @Test + public void createComponent_shouldFail_whenCreatingComponentWithMultipleExistingKeyButDifferentCase() { + String existingKey = randomAlphabetic(5).toUpperCase(); + String existingKeyLowerCase = existingKey.toLowerCase(); + db.components().insertPrivateProject(component -> component.setKey(existingKey)); + db.components().insertPrivateProject(component -> component.setKey(existingKeyLowerCase)); + String newKey = StringUtils.capitalize(existingKeyLowerCase); + + NewComponent project = NewComponent.newComponentBuilder() + .setKey(newKey) + .setName(DEFAULT_PROJECT_NAME) + .build(); + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(project) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + + DbSession dbSession = db.getSession(); + assertThatThrownBy(() -> underTest.create(dbSession, creationParameters)) + .isInstanceOf(BadRequestException.class) + .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s, %s\"", newKey, existingKey, existingKeyLowerCase); + } + + @Test + public void createComponent_shouldFail_whenCreatingComponentWithMultipleExistingPortfolioKeysButDifferentCase() { + String existingKey = randomAlphabetic(5).toUpperCase(); + String existingKeyLowerCase = existingKey.toLowerCase(); + db.components().insertPrivatePortfolio(portfolio -> portfolio.setKey(existingKey)); + db.components().insertPrivatePortfolio(portfolio -> portfolio.setKey(existingKeyLowerCase)); + String newKey = StringUtils.capitalize(existingKeyLowerCase); + + NewComponent project = NewComponent.newComponentBuilder() + .setKey(newKey) + .setName(DEFAULT_PROJECT_NAME) + .build(); + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(project) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + + DbSession dbSession = db.getSession(); + assertThatThrownBy(() -> underTest.create(dbSession, creationParameters)) + .isInstanceOf(BadRequestException.class) + .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s, %s\"", newKey, existingKey, existingKeyLowerCase); + } + + @Test + public void create_createsComponentWithMasterBranchName() { + String componentNameAndKey = "createApplicationOrPortfolio"; + NewComponent app = NewComponent.newComponentBuilder() + .setKey(componentNameAndKey) + .setName(componentNameAndKey) + .setQualifier("APP") + .build(); + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(app) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + + ComponentDto appDto = underTest.create(db.getSession(), creationParameters).mainBranchComponent(); + + Optional branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), appDto.branchUuid()); + assertThat(branch).isPresent(); + assertThat(branch.get().getBranchKey()).isEqualTo(DEFAULT_MAIN_BRANCH_NAME); + } + + @Test + public void createWithoutCommit_whenProjectIsManaged_doesntApplyPermissionTemplate() { + UserDto userDto = db.users().insertUser(); + ComponentCreationParameters componentCreationParameters = ComponentCreationParameters.builder() + .newComponent(DEFAULT_COMPONENT) + .userLogin(userDto.getLogin()) + .userUuid(userDto.getUuid()) + .mainBranchName(null) + .isManaged(true) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + underTest.createWithoutCommit(db.getSession(), componentCreationParameters); + + verify(permissionTemplateService, never()).applyDefaultToNewComponent(any(), any(), any()); + } + + @Test + public void createWithoutCommit_whenInsertingPortfolio_shouldOnlyAddOneEntryToAuditLogs() { + String portfolioKey = "portfolio"; + NewComponent portfolio = NewComponent.newComponentBuilder() + .setKey(portfolioKey) + .setName(portfolioKey) + .setQualifier(VIEW) + .build(); + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(portfolio) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + + underTest.createWithoutCommit(db.getSession(), creationParameters); + db.commit(); + + verify(auditPersister, times(1)).addComponent(argThat(d -> d.equals(db.getSession())), + argThat(newValue -> newValue.getComponentKey().equals(portfolioKey))); + } + + @Test + public void createWithoutCommit_whenProjectIsManagedAndPrivate_applyPublicPermissionsToCreator() { + UserDto userDto = db.users().insertUser(); + NewComponent newComponent = NewComponent.newComponentBuilder() + .setKey(DEFAULT_PROJECT_KEY) + .setName(DEFAULT_PROJECT_NAME) + .setPrivate(true) + .build(); + + DbSession session = db.getSession(); + + ComponentCreationParameters componentCreationParameters = ComponentCreationParameters.builder() + .newComponent(PRIVATE_COMPONENT) + .userLogin(userDto.getLogin()) + .userUuid(userDto.getUuid()) + .mainBranchName(null) + .isManaged(true) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + ComponentCreationData componentCreationData = underTest.createWithoutCommit(session, componentCreationParameters); + + List permissions = db.getDbClient().userPermissionDao().selectEntityPermissionsOfUser(session, userDto.getUuid(), componentCreationData.projectDto().getUuid()); + assertThat(permissions) + .containsExactlyInAnyOrder(UserRole.USER, UserRole.CODEVIEWER); + } + + @Test + public void create_whenCreationMethodIsLocalApi_persistsIt() { + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(DEFAULT_COMPONENT) + .creationMethod(CreationMethod.LOCAL_API) + .build(); + ProjectDto projectDto = underTest.create(db.getSession(), creationParameters).projectDto(); + assertThat(projectDto.getCreationMethod()).isEqualTo(CreationMethod.LOCAL_API); + } + + @Test + public void create_whenCreationMethodIsAlmImportBrowser_persistsIt() { + ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() + .newComponent(DEFAULT_COMPONENT) + .creationMethod(CreationMethod.ALM_IMPORT_BROWSER) + .build(); + ProjectDto projectDto = underTest.create(db.getSession(), creationParameters).projectDto(); + assertThat(projectDto.getCreationMethod()).isEqualTo(CreationMethod.ALM_IMPORT_BROWSER); + } +} diff --git a/server/sonar-webserver-common/src/it/java/org/sonar/server/common/permission/DefaultTemplatesResolverImplIT.java b/server/sonar-webserver-common/src/it/java/org/sonar/server/common/permission/DefaultTemplatesResolverImplIT.java new file mode 100644 index 00000000000..2a3716ab1af --- /dev/null +++ b/server/sonar-webserver-common/src/it/java/org/sonar/server/common/permission/DefaultTemplatesResolverImplIT.java @@ -0,0 +1,110 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.permission; + +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.component.ResourceTypesRule; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.sonar.api.resources.Qualifiers.APP; +import static org.sonar.api.resources.Qualifiers.PROJECT; +import static org.sonar.api.resources.Qualifiers.VIEW; + +public class DefaultTemplatesResolverImplIT { + + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + + private ResourceTypesRule resourceTypesWithPortfoliosInstalled = new ResourceTypesRule().setRootQualifiers(PROJECT, APP, VIEW); + private ResourceTypesRule resourceTypesWithApplicationInstalled = new ResourceTypesRule().setRootQualifiers(PROJECT, APP); + private ResourceTypesRule resourceTypes = new ResourceTypesRule().setRootQualifiers(PROJECT); + + private DefaultTemplatesResolverImpl underTestWithPortfoliosInstalled = new DefaultTemplatesResolverImpl(db.getDbClient(), resourceTypesWithPortfoliosInstalled); + private DefaultTemplatesResolverImpl underTestWithApplicationInstalled = new DefaultTemplatesResolverImpl(db.getDbClient(), resourceTypesWithApplicationInstalled); + private DefaultTemplatesResolverImpl underTest = new DefaultTemplatesResolverImpl(db.getDbClient(), resourceTypes); + + @Test + public void get_default_templates_when_portfolio_not_installed() { + db.permissionTemplates().setDefaultTemplates("prj", null, null); + + assertThat(underTest.resolve(db.getSession()).getProject()).contains("prj"); + assertThat(underTest.resolve(db.getSession()).getApplication()).isEmpty(); + assertThat(underTest.resolve(db.getSession()).getPortfolio()).isEmpty(); + } + + @Test + public void get_default_templates_always_return_project_template_even_when_all_templates_are_defined_but_portfolio_not_installed() { + db.permissionTemplates().setDefaultTemplates("prj", "app", "port"); + + assertThat(underTest.resolve(db.getSession()).getProject()).contains("prj"); + assertThat(underTest.resolve(db.getSession()).getApplication()).isEmpty(); + assertThat(underTest.resolve(db.getSession()).getPortfolio()).isEmpty(); + } + + @Test + public void get_default_templates_always_return_project_template_when_only_project_template_and_portfolio_is_installed_() { + db.permissionTemplates().setDefaultTemplates("prj", null, null); + + assertThat(underTestWithPortfoliosInstalled.resolve(db.getSession()).getProject()).contains("prj"); + assertThat(underTestWithPortfoliosInstalled.resolve(db.getSession()).getApplication()).contains("prj"); + assertThat(underTestWithPortfoliosInstalled.resolve(db.getSession()).getPortfolio()).contains("prj"); + } + + @Test + public void get_default_templates_for_all_components_when_portfolio_is_installed() { + db.permissionTemplates().setDefaultTemplates("prj", "app", "port"); + + assertThat(underTestWithPortfoliosInstalled.resolve(db.getSession()).getProject()).contains("prj"); + assertThat(underTestWithPortfoliosInstalled.resolve(db.getSession()).getApplication()).contains("app"); + assertThat(underTestWithPortfoliosInstalled.resolve(db.getSession()).getPortfolio()).contains("port"); + } + + @Test + public void get_default_templates_always_return_project_template_when_only_project_template_and_application_is_installed_() { + db.permissionTemplates().setDefaultTemplates("prj", null, null); + + assertThat(underTestWithApplicationInstalled.resolve(db.getSession()).getProject()).contains("prj"); + assertThat(underTestWithApplicationInstalled.resolve(db.getSession()).getApplication()).contains("prj"); + assertThat(underTestWithApplicationInstalled.resolve(db.getSession()).getPortfolio()).isEmpty(); + } + + @Test + public void get_default_templates_for_all_components_when_application_is_installed() { + db.permissionTemplates().setDefaultTemplates("prj", "app", null); + + assertThat(underTestWithApplicationInstalled.resolve(db.getSession()).getProject()).contains("prj"); + assertThat(underTestWithApplicationInstalled.resolve(db.getSession()).getApplication()).contains("app"); + assertThat(underTestWithApplicationInstalled.resolve(db.getSession()).getPortfolio()).isEmpty(); + } + + @Test + public void fail_when_default_template_for_project_is_missing() { + DbSession session = db.getSession(); + assertThatThrownBy(() -> underTestWithPortfoliosInstalled.resolve(session)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Default template for project is missing"); + } + +} diff --git a/server/sonar-webserver-common/src/it/java/org/sonar/server/common/permission/GroupPermissionChangerIT.java b/server/sonar-webserver-common/src/it/java/org/sonar/server/common/permission/GroupPermissionChangerIT.java new file mode 100644 index 00000000000..03e760bea6b --- /dev/null +++ b/server/sonar-webserver-common/src/it/java/org/sonar/server/common/permission/GroupPermissionChangerIT.java @@ -0,0 +1,408 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.permission; + +import java.util.Set; +import org.apache.commons.lang3.StringUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.ResourceTypes; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +import org.sonar.core.util.SequenceUuidFactory; +import org.sonar.core.util.Uuids; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.component.ResourceTypesRule; +import org.sonar.db.permission.GlobalPermission; +import org.sonar.db.permission.GroupPermissionDto; +import org.sonar.db.project.ProjectDto; +import org.sonar.db.user.GroupDto; +import org.sonar.db.user.UserDto; +import org.sonar.server.common.permission.GroupPermissionChange; +import org.sonar.server.common.permission.GroupPermissionChanger; +import org.sonar.server.common.permission.Operation; +import org.sonar.server.exceptions.BadRequestException; +import org.sonar.server.permission.PermissionService; +import org.sonar.server.permission.PermissionServiceImpl; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.fail; +import static org.sonar.db.permission.GlobalPermission.ADMINISTER; +import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_GATES; +import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES; +import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; + +public class GroupPermissionChangerIT { + + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + + private final ResourceTypes resourceTypes = new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT); + private final PermissionService permissionService = new PermissionServiceImpl(resourceTypes); + private final GroupPermissionChanger underTest = new GroupPermissionChanger(db.getDbClient(), new SequenceUuidFactory()); + private GroupDto group; + + private ProjectDto privateProject; + private ProjectDto publicProject; + + @Before + public void setUp() { + group = db.users().insertGroup("a-group"); + privateProject = db.components().insertPrivateProject().getProjectDto(); + publicProject = db.components().insertPublicProject().getProjectDto(); + } + + @Test + public void apply_adds_global_permission_to_group() { + apply(new GroupPermissionChange(Operation.ADD, ADMINISTER_QUALITY_PROFILES.getKey(), null, group, permissionService)); + + assertThat(db.users().selectGroupPermissions(group, null)).containsOnly(ADMINISTER_QUALITY_PROFILES.getKey()); + } + + @Test + public void apply_adds_global_permission_to_group_AnyOne() { + apply(new GroupPermissionChange(Operation.ADD, ADMINISTER_QUALITY_PROFILES.getKey(), null, null, permissionService)); + + assertThat(db.users().selectAnyonePermissions(null)).containsOnly(ADMINISTER_QUALITY_PROFILES.getKey()); + } + + @Test + public void apply_fails_with_BadRequestException_when_adding_any_permission_to_group_AnyOne_on_private_project() { + permissionService.getAllProjectPermissions() + .forEach(perm -> { + GroupPermissionChange change = new GroupPermissionChange(Operation.ADD, perm, privateProject, null, permissionService); + try { + apply(change); + fail("a BadRequestException should have been thrown"); + } catch (BadRequestException e) { + assertThat(e).hasMessage("No permission can be granted to Anyone on a private component"); + } + }); + } + + @Test + public void apply_has_no_effect_when_removing_any_permission_to_group_AnyOne_on_private_project() { + permissionService.getAllProjectPermissions() + .forEach(this::unsafeInsertProjectPermissionOnAnyone); + + permissionService.getAllProjectPermissions() + .forEach(perm -> { + apply(new GroupPermissionChange(Operation.REMOVE, perm, privateProject, null, permissionService)); + + assertThat(db.users().selectAnyonePermissions(privateProject.getUuid())).contains(perm); + }); + } + + @Test + public void apply_adds_permission_USER_to_group_on_private_project() { + applyAddsPermissionToGroupOnPrivateProject(UserRole.USER); + } + + @Test + public void apply_adds_permission_CODEVIEWER_to_group_on_private_project() { + applyAddsPermissionToGroupOnPrivateProject(UserRole.CODEVIEWER); + } + + @Test + public void apply_adds_permission_ADMIN_to_group_on_private_project() { + applyAddsPermissionToGroupOnPrivateProject(UserRole.ADMIN); + } + + @Test + public void apply_adds_permission_ISSUE_ADMIN_to_group_on_private_project() { + applyAddsPermissionToGroupOnPrivateProject(UserRole.ISSUE_ADMIN); + } + + @Test + public void apply_adds_permission_SCAN_EXECUTION_to_group_on_private_project() { + applyAddsPermissionToGroupOnPrivateProject(GlobalPermission.SCAN.getKey()); + } + + private void applyAddsPermissionToGroupOnPrivateProject(String permission) { + + apply(new GroupPermissionChange(Operation.ADD, permission, privateProject, group, permissionService)); + + assertThat(db.users().selectGroupPermissions(group, null)).isEmpty(); + assertThat(db.users().selectGroupPermissions(group, privateProject)).containsOnly(permission); + } + + @Test + public void apply_removes_permission_USER_from_group_on_private_project() { + applyRemovesPermissionFromGroupOnPrivateProject(UserRole.USER); + } + + @Test + public void apply_removes_permission_CODEVIEWER_from_group_on_private_project() { + applyRemovesPermissionFromGroupOnPrivateProject(UserRole.CODEVIEWER); + } + + @Test + public void apply_removes_permission_ADMIN_from_on_private_project() { + applyRemovesPermissionFromGroupOnPrivateProject(UserRole.ADMIN); + } + + @Test + public void apply_removes_permission_ISSUE_ADMIN_from_on_private_project() { + applyRemovesPermissionFromGroupOnPrivateProject(UserRole.ISSUE_ADMIN); + } + + @Test + public void apply_removes_permission_SCAN_EXECUTION_from_on_private_project() { + applyRemovesPermissionFromGroupOnPrivateProject(GlobalPermission.SCAN.getKey()); + } + + private void applyRemovesPermissionFromGroupOnPrivateProject(String permission) { + db.users().insertEntityPermissionOnGroup(group, permission, privateProject); + + apply(new GroupPermissionChange(Operation.ADD, permission, privateProject, group, permissionService), permission); + + assertThat(db.users().selectGroupPermissions(group, privateProject)).containsOnly(permission); + } + + @Test + public void apply_has_no_effect_when_adding_USER_permission_to_group_AnyOne_on_a_public_project() { + apply(new GroupPermissionChange(Operation.ADD, UserRole.USER, publicProject, null, permissionService)); + + assertThat(db.users().selectAnyonePermissions(publicProject.getUuid())).isEmpty(); + } + + @Test + public void apply_has_no_effect_when_adding_CODEVIEWER_permission_to_group_AnyOne_on_a_public_project() { + apply(new GroupPermissionChange(Operation.ADD, UserRole.CODEVIEWER, publicProject, null, permissionService)); + + assertThat(db.users().selectAnyonePermissions(publicProject.getUuid())).isEmpty(); + } + + @Test + public void apply_fails_with_BadRequestException_when_adding_permission_ADMIN_to_group_AnyOne_on_a_public_project() { + GroupPermissionChange change = new GroupPermissionChange(Operation.ADD, UserRole.ADMIN, publicProject, null, permissionService); + assertThatThrownBy(() -> apply(change)) + .isInstanceOf(BadRequestException.class) + .hasMessage("It is not possible to add the 'admin' permission to group 'Anyone'."); + } + + @Test + public void apply_adds_permission_ISSUE_ADMIN_to_group_AnyOne_on_a_public_project() { + apply(new GroupPermissionChange(Operation.ADD, UserRole.ISSUE_ADMIN, publicProject, null, permissionService)); + + assertThat(db.users().selectAnyonePermissions(publicProject.getUuid())).containsOnly(UserRole.ISSUE_ADMIN); + } + + @Test + public void apply_adds_permission_SCAN_EXECUTION_to_group_AnyOne_on_a_public_project() { + apply(new GroupPermissionChange(Operation.ADD, GlobalPermission.SCAN.getKey(), publicProject, null, permissionService)); + + assertThat(db.users().selectAnyonePermissions(publicProject.getUuid())).containsOnly(GlobalPermission.SCAN.getKey()); + } + + @Test + public void apply_fails_with_BadRequestException_when_removing_USER_permission_from_group_AnyOne_on_a_public_project() { + GroupPermissionChange change = new GroupPermissionChange(Operation.REMOVE, UserRole.USER, publicProject, null, permissionService); + assertThatThrownBy(() -> apply(change)) + .isInstanceOf(BadRequestException.class) + .hasMessage("Permission user can't be removed from a public component"); + } + + @Test + public void apply_fails_with_BadRequestException_when_removing_CODEVIEWER_permission_from_group_AnyOne_on_a_public_project() { + GroupPermissionChange change = new GroupPermissionChange(Operation.REMOVE, UserRole.CODEVIEWER, publicProject, null, permissionService); + assertThatThrownBy(() -> apply(change)) + .isInstanceOf(BadRequestException.class) + .hasMessage("Permission codeviewer can't be removed from a public component"); + } + + @Test + public void apply_removes_ADMIN_permission_from_group_AnyOne_on_a_public_project() { + applyRemovesPermissionFromGroupAnyOneOnAPublicProject(UserRole.ADMIN); + } + + @Test + public void apply_removes_ISSUE_ADMIN_permission_from_group_AnyOne_on_a_public_project() { + applyRemovesPermissionFromGroupAnyOneOnAPublicProject(UserRole.ISSUE_ADMIN); + } + + @Test + public void apply_removes_SCAN_EXECUTION_permission_from_group_AnyOne_on_a_public_project() { + applyRemovesPermissionFromGroupAnyOneOnAPublicProject(GlobalPermission.SCAN.getKey()); + } + + private void applyRemovesPermissionFromGroupAnyOneOnAPublicProject(String permission) { + db.users().insertEntityPermissionOnAnyone(permission, publicProject); + + apply(new GroupPermissionChange(Operation.REMOVE, permission, publicProject, null, permissionService), permission); + + assertThat(db.users().selectAnyonePermissions(publicProject.getUuid())).isEmpty(); + } + + @Test + public void apply_fails_with_BadRequestException_when_removing_USER_permission_from_a_group_on_a_public_project() { + GroupPermissionChange change = new GroupPermissionChange(Operation.REMOVE, UserRole.USER, publicProject, group, permissionService); + assertThatThrownBy(() -> apply(change)) + .isInstanceOf(BadRequestException.class) + .hasMessage("Permission user can't be removed from a public component"); + } + + @Test + public void apply_fails_with_BadRequestException_when_removing_CODEVIEWER_permission_from_a_group_on_a_public_project() { + GroupPermissionChange change = new GroupPermissionChange(Operation.REMOVE, UserRole.CODEVIEWER, publicProject, group, permissionService); + assertThatThrownBy(() -> apply(change)) + .isInstanceOf(BadRequestException.class) + .hasMessage("Permission codeviewer can't be removed from a public component"); + } + + @Test + public void add_permission_to_anyone() { + apply(new GroupPermissionChange(Operation.ADD, ADMINISTER_QUALITY_PROFILES.getKey(), null, null, permissionService)); + + assertThat(db.users().selectGroupPermissions(group, null)).isEmpty(); + assertThat(db.users().selectAnyonePermissions(null)).containsOnly(ADMINISTER_QUALITY_PROFILES.getKey()); + } + + @Test + public void do_nothing_when_adding_permission_that_already_exists() { + db.users().insertPermissionOnGroup(group, ADMINISTER_QUALITY_GATES); + + apply(new GroupPermissionChange(Operation.ADD, ADMINISTER_QUALITY_GATES.getKey(), null, group, permissionService), ADMINISTER_QUALITY_GATES.getKey()); + + assertThat(db.users().selectGroupPermissions(group, null)).containsExactly(ADMINISTER_QUALITY_GATES.getKey()); + } + + @Test + public void fail_to_add_global_permission_but_SCAN_and_ADMIN_on_private_project() { + permissionService.getGlobalPermissions().stream() + .map(GlobalPermission::getKey) + .filter(perm -> !UserRole.ADMIN.equals(perm) && !GlobalPermission.SCAN.getKey().equals(perm)) + .forEach(perm -> { + try { + new GroupPermissionChange(Operation.ADD, perm, privateProject, group, permissionService); + fail("a BadRequestException should have been thrown for permission " + perm); + } catch (BadRequestException e) { + assertThat(e).hasMessage("Invalid project permission '" + perm + + "'. Valid values are [" + StringUtils.join(permissionService.getAllProjectPermissions(), ", ") + "]"); + } + }); + } + + @Test + public void fail_to_add_global_permission_but_SCAN_and_ADMIN_on_public_project() { + permissionService.getGlobalPermissions().stream() + .map(GlobalPermission::getKey) + .filter(perm -> !UserRole.ADMIN.equals(perm) && !GlobalPermission.SCAN.getKey().equals(perm)) + .forEach(perm -> { + try { + new GroupPermissionChange(Operation.ADD, perm, publicProject, group, permissionService); + fail("a BadRequestException should have been thrown for permission " + perm); + } catch (BadRequestException e) { + assertThat(e).hasMessage("Invalid project permission '" + perm + + "'. Valid values are [" + StringUtils.join(permissionService.getAllProjectPermissions(), ", ") + "]"); + } + }); + } + + @Test + public void fail_to_add_project_permission_but_SCAN_and_ADMIN_on_global_group() { + permissionService.getAllProjectPermissions() + .stream() + .filter(perm -> !GlobalPermission.SCAN.getKey().equals(perm) && !GlobalPermission.ADMINISTER.getKey().equals(perm)) + .forEach(permission -> { + try { + new GroupPermissionChange(Operation.ADD, permission, null, group, permissionService); + fail("a BadRequestException should have been thrown for permission " + permission); + } catch (BadRequestException e) { + assertThat(e).hasMessage("Invalid global permission '" + permission + "'. Valid values are [admin, gateadmin, profileadmin, provisioning, scan]"); + } + }); + } + + @Test + public void remove_permission_from_group() { + db.users().insertPermissionOnGroup(group, ADMINISTER_QUALITY_GATES); + db.users().insertPermissionOnGroup(group, PROVISION_PROJECTS); + + apply(new GroupPermissionChange(Operation.REMOVE, ADMINISTER_QUALITY_GATES.getKey(), null, group, permissionService), ADMINISTER_QUALITY_GATES.getKey(), + PROVISION_PROJECTS.getKey()); + + assertThat(db.users().selectGroupPermissions(group, null)).containsOnly(PROVISION_PROJECTS.getKey()); + } + + @Test + public void remove_project_permission_from_group() { + db.users().insertPermissionOnGroup(group, ADMINISTER_QUALITY_GATES); + db.users().insertEntityPermissionOnGroup(group, UserRole.ISSUE_ADMIN, privateProject); + db.users().insertEntityPermissionOnGroup(group, UserRole.CODEVIEWER, privateProject); + + apply(new GroupPermissionChange(Operation.REMOVE, UserRole.ISSUE_ADMIN, privateProject, group, permissionService), UserRole.ISSUE_ADMIN, + UserRole.CODEVIEWER); + + assertThat(db.users().selectGroupPermissions(group, null)).containsOnly(ADMINISTER_QUALITY_GATES.getKey()); + assertThat(db.users().selectGroupPermissions(group, privateProject)).containsOnly(UserRole.CODEVIEWER); + } + + @Test + public void do_not_fail_if_removing_a_permission_that_does_not_exist() { + apply(new GroupPermissionChange(Operation.REMOVE, UserRole.ISSUE_ADMIN, privateProject, group, permissionService)); + + assertThat(db.users().selectGroupPermissions(group, null)).isEmpty(); + assertThat(db.users().selectGroupPermissions(group, privateProject)).isEmpty(); + } + + @Test + public void fail_to_remove_admin_permission_if_no_more_admins() { + db.users().insertPermissionOnGroup(group, ADMINISTER); + + GroupPermissionChange change = new GroupPermissionChange(Operation.REMOVE, ADMINISTER.getKey(), null, group, permissionService); + Set permission = Set.of("admin"); + DbSession session = db.getSession(); + assertThatThrownBy(() -> underTest.apply(session, permission, change)) + .isInstanceOf(BadRequestException.class) + .hasMessage("Last group with permission 'admin'. Permission cannot be removed."); + } + + @Test + public void remove_admin_group_if_still_other_admins() { + db.users().insertPermissionOnGroup(group, ADMINISTER); + UserDto admin = db.users().insertUser(); + db.users().insertGlobalPermissionOnUser(admin, ADMINISTER); + + apply(new GroupPermissionChange(Operation.REMOVE, ADMINISTER.getKey(), null, group, permissionService), ADMINISTER.getKey()); + + assertThat(db.users().selectGroupPermissions(group, null)).isEmpty(); + } + + private void apply(GroupPermissionChange change, String... existingPermissions) { + underTest.apply(db.getSession(), Set.of(existingPermissions), change); + db.commit(); + } + + private void unsafeInsertProjectPermissionOnAnyone(String perm) { + GroupPermissionDto dto = new GroupPermissionDto() + .setUuid(Uuids.createFast()) + .setGroupUuid(null) + .setRole(perm) + .setEntityUuid(privateProject.getUuid()) + .setEntityName(privateProject.getName()); + db.getDbClient().groupPermissionDao().insert(db.getSession(), dto, privateProject, null); + db.commit(); + } +} diff --git a/server/sonar-webserver-common/src/it/java/org/sonar/server/common/permission/PermissionTemplateServiceIT.java b/server/sonar-webserver-common/src/it/java/org/sonar/server/common/permission/PermissionTemplateServiceIT.java new file mode 100644 index 00000000000..d7095c13bcd --- /dev/null +++ b/server/sonar-webserver-common/src/it/java/org/sonar/server/common/permission/PermissionTemplateServiceIT.java @@ -0,0 +1,484 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.permission; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.web.UserRole; +import org.sonar.core.util.SequenceUuidFactory; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.component.ResourceTypesRule; +import org.sonar.db.permission.GlobalPermission; +import org.sonar.db.permission.template.PermissionTemplateDbTester; +import org.sonar.db.permission.template.PermissionTemplateDto; +import org.sonar.db.portfolio.PortfolioDto; +import org.sonar.db.project.ProjectDto; +import org.sonar.db.user.GroupDto; +import org.sonar.db.user.UserDto; +import org.sonar.server.es.Indexers; +import org.sonar.server.es.TestIndexers; +import org.sonar.server.exceptions.TemplateMatchingKeyException; +import org.sonar.server.permission.PermissionService; +import org.sonar.server.permission.PermissionServiceImpl; +import org.sonar.server.tester.UserSessionRule; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.sonar.api.resources.Qualifiers.APP; +import static org.sonar.api.resources.Qualifiers.PROJECT; +import static org.sonar.api.resources.Qualifiers.VIEW; + +public class PermissionTemplateServiceIT { + + @Rule + public DbTester dbTester = DbTester.create(); + + private final ResourceTypesRule resourceTypesRule = new ResourceTypesRule().setRootQualifiers(PROJECT, VIEW, APP); + private final DefaultTemplatesResolver defaultTemplatesResolver = new DefaultTemplatesResolverImpl(dbTester.getDbClient(), resourceTypesRule); + private final PermissionService permissionService = new PermissionServiceImpl(resourceTypesRule); + private final UserSessionRule userSession = UserSessionRule.standalone(); + private final PermissionTemplateDbTester templateDb = dbTester.permissionTemplates(); + private final DbSession session = dbTester.getSession(); + private final Indexers indexers = new TestIndexers(); + private final PermissionTemplateService underTest = new PermissionTemplateService(dbTester.getDbClient(), indexers, userSession, defaultTemplatesResolver, + new SequenceUuidFactory()); + + @Test + public void apply_does_not_insert_permission_to_group_AnyOne_when_applying_template_on_private_project() { + ProjectDto privateProject = dbTester.components().insertPrivateProject().getProjectDto(); + PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); + dbTester.permissionTemplates().addAnyoneToTemplate(permissionTemplate, "p1"); + + underTest.applyAndCommit(session, permissionTemplate, singletonList(privateProject)); + + assertThat(selectProjectPermissionsOfGroup(null, privateProject.getUuid())).isEmpty(); + } + + @Test + public void apply_default_does_not_insert_permission_to_group_AnyOne_when_applying_template_on_private_project() { + ProjectDto privateProject = dbTester.components().insertPrivateProject().getProjectDto(); + UserDto creator = dbTester.users().insertUser(); + PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); + dbTester.permissionTemplates().addAnyoneToTemplate(permissionTemplate, "p1"); + dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); + + underTest.applyDefaultToNewComponent(session, privateProject, creator.getUuid()); + + assertThat(selectProjectPermissionsOfGroup(null, privateProject.getUuid())).isEmpty(); + } + + @Test + public void apply_inserts_permissions_to_group_AnyOne_but_USER_and_CODEVIEWER_when_applying_template_on_public_project() { + ProjectDto publicProject = dbTester.components().insertPublicProject().getProjectDto(); + PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); + permissionService.getAllProjectPermissions() + .forEach(perm -> dbTester.permissionTemplates().addAnyoneToTemplate(permissionTemplate, perm)); + dbTester.permissionTemplates().addAnyoneToTemplate(permissionTemplate, "p1"); + + underTest.applyAndCommit(session, permissionTemplate, singletonList(publicProject)); + + assertThat(selectProjectPermissionsOfGroup(null, publicProject.getUuid())) + .containsOnly("p1", UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); + } + + @Test + public void applyDefault_inserts_permissions_to_group_AnyOne_but_USER_and_CODEVIEWER_when_applying_template_on_public_project() { + ProjectDto publicProject = dbTester.components().insertPublicProject().getProjectDto(); + PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); + permissionService.getAllProjectPermissions() + .forEach(perm -> dbTester.permissionTemplates().addAnyoneToTemplate(permissionTemplate, perm)); + dbTester.permissionTemplates().addAnyoneToTemplate(permissionTemplate, "p1"); + dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); + + underTest.applyDefaultToNewComponent(session, publicProject, null); + + assertThat(selectProjectPermissionsOfGroup(null, publicProject.getUuid())) + .containsOnly("p1", UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); + } + + @Test + public void apply_inserts_any_permissions_to_group_when_applying_template_on_private_project() { + ProjectDto privateProject = dbTester.components().insertPrivateProject().getProjectDto(); + GroupDto group = dbTester.users().insertGroup(); + PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); + permissionService.getAllProjectPermissions() + .forEach(perm -> dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, perm)); + dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, "p1"); + + underTest.applyAndCommit(session, permissionTemplate, singletonList(privateProject)); + + assertThat(selectProjectPermissionsOfGroup(group, privateProject.getUuid())) + .containsOnly("p1", UserRole.CODEVIEWER, UserRole.USER, UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); + } + + @Test + public void applyDefault_inserts_any_permissions_to_group_when_applying_template_on_private_project() { + GroupDto group = dbTester.users().insertGroup(); + ProjectDto privateProject = dbTester.components().insertPrivateProject().getProjectDto(); + PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); + permissionService.getAllProjectPermissions() + .forEach(perm -> dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, perm)); + dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, "p1"); + dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); + + underTest.applyDefaultToNewComponent(session, privateProject, null); + + assertThat(selectProjectPermissionsOfGroup(group, privateProject.getUuid())) + .containsOnly("p1", UserRole.CODEVIEWER, UserRole.USER, UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); + } + + @Test + public void apply_inserts_permissions_to_group_but_USER_and_CODEVIEWER_when_applying_template_on_public_project() { + PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); + ProjectDto publicProject = dbTester.components().insertPublicProject().getProjectDto(); + GroupDto group = dbTester.users().insertGroup(); + permissionService.getAllProjectPermissions() + .forEach(perm -> dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, perm)); + dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, "p1"); + + underTest.applyAndCommit(session, permissionTemplate, singletonList(publicProject)); + + assertThat(selectProjectPermissionsOfGroup(group, publicProject.getUuid())) + .containsOnly("p1", UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); + } + + @Test + public void applyDefault_inserts_permissions_to_group_but_USER_and_CODEVIEWER_when_applying_template_on_public_project() { + PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); + ProjectDto publicProject = dbTester.components().insertPublicProject().getProjectDto(); + GroupDto group = dbTester.users().insertGroup(); + permissionService.getAllProjectPermissions() + .forEach(perm -> dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, perm)); + dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, "p1"); + dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); + + underTest.applyDefaultToNewComponent(session, publicProject, null); + + assertThat(selectProjectPermissionsOfGroup(group, publicProject.getUuid())) + .containsOnly("p1", UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); + } + + @Test + public void apply_inserts_permissions_to_user_but_USER_and_CODEVIEWER_when_applying_template_on_public_project() { + PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); + ProjectDto publicProject = dbTester.components().insertPublicProject().getProjectDto(); + UserDto user = dbTester.users().insertUser(); + permissionService.getAllProjectPermissions() + .forEach(perm -> dbTester.permissionTemplates().addUserToTemplate(permissionTemplate, user, perm)); + dbTester.permissionTemplates().addUserToTemplate(permissionTemplate, user, "p1"); + + underTest.applyAndCommit(session, permissionTemplate, singletonList(publicProject)); + + assertThat(selectProjectPermissionsOfUser(user, publicProject.getUuid())) + .containsOnly("p1", UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); + } + + @Test + public void applyDefault_inserts_permissions_to_user_but_USER_and_CODEVIEWER_when_applying_template_on_public_project() { + PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); + ProjectDto publicProject = dbTester.components().insertPublicProject().getProjectDto(); + UserDto user = dbTester.users().insertUser(); + permissionService.getAllProjectPermissions() + .forEach(perm -> dbTester.permissionTemplates().addUserToTemplate(permissionTemplate, user, perm)); + dbTester.permissionTemplates().addUserToTemplate(permissionTemplate, user, "p1"); + dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); + + underTest.applyDefaultToNewComponent(session, publicProject, null); + + assertThat(selectProjectPermissionsOfUser(user, publicProject.getUuid())) + .containsOnly("p1", UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); + } + + @Test + public void apply_inserts_any_permissions_to_user_when_applying_template_on_private_project() { + PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); + ProjectDto privateProject = dbTester.components().insertPrivateProject().getProjectDto(); + UserDto user = dbTester.users().insertUser(); + permissionService.getAllProjectPermissions() + .forEach(perm -> dbTester.permissionTemplates().addUserToTemplate(permissionTemplate, user, perm)); + dbTester.permissionTemplates().addUserToTemplate(permissionTemplate, user, "p1"); + + underTest.applyAndCommit(session, permissionTemplate, singletonList(privateProject)); + + assertThat(selectProjectPermissionsOfUser(user, privateProject.getUuid())) + .containsOnly("p1", UserRole.CODEVIEWER, UserRole.USER, UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); + } + + @Test + public void applyDefault_inserts_any_permissions_to_user_when_applying_template_on_private_project() { + PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); + ProjectDto privateProject = dbTester.components().insertPrivateProject().getProjectDto(); + UserDto user = dbTester.users().insertUser(); + permissionService.getAllProjectPermissions() + .forEach(perm -> dbTester.permissionTemplates().addUserToTemplate(permissionTemplate, user, perm)); + dbTester.permissionTemplates().addUserToTemplate(permissionTemplate, user, "p1"); + dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); + + underTest.applyDefaultToNewComponent(session, privateProject, null); + + assertThat(selectProjectPermissionsOfUser(user, privateProject.getUuid())) + .containsOnly("p1", UserRole.CODEVIEWER, UserRole.USER, UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); + } + + @Test + public void applyDefault_inserts_permissions_to_ProjectCreator_but_USER_and_CODEVIEWER_when_applying_template_on_public_project() { + PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); + ProjectDto publicProject = dbTester.components().insertPublicProject().getProjectDto(); + UserDto user = dbTester.users().insertUser(); + permissionService.getAllProjectPermissions() + .forEach(perm -> dbTester.permissionTemplates().addProjectCreatorToTemplate(permissionTemplate, perm)); + dbTester.permissionTemplates().addProjectCreatorToTemplate(permissionTemplate, "p1"); + dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); + + underTest.applyDefaultToNewComponent(session, publicProject, user.getUuid()); + + assertThat(selectProjectPermissionsOfUser(user, publicProject.getUuid())) + .containsOnly("p1", UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); + } + + @Test + public void applyDefault_inserts_any_permissions_to_ProjectCreator_when_applying_template_on_private_project() { + PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); + ProjectDto privateProject = dbTester.components().insertPrivateProject().getProjectDto(); + UserDto user = dbTester.users().insertUser(); + permissionService.getAllProjectPermissions() + .forEach(perm -> dbTester.permissionTemplates().addProjectCreatorToTemplate(permissionTemplate, perm)); + dbTester.permissionTemplates().addProjectCreatorToTemplate(permissionTemplate, "p1"); + dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); + + underTest.applyDefaultToNewComponent(session, privateProject, user.getUuid()); + + assertThat(selectProjectPermissionsOfUser(user, privateProject.getUuid())) + .containsOnly("p1", UserRole.CODEVIEWER, UserRole.USER, UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); + } + + @Test + public void apply_template_on_view() { + PortfolioDto portfolio = dbTester.components().insertPrivatePortfolioDto(); + PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); + GroupDto group = dbTester.users().insertGroup(); + dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, GlobalPermission.ADMINISTER.getKey()); + dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, GlobalPermission.PROVISION_PROJECTS.getKey()); + dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); + + underTest.applyDefaultToNewComponent(session, portfolio, null); + + assertThat(selectProjectPermissionsOfGroup(group, portfolio.getUuid())) + .containsOnly(GlobalPermission.ADMINISTER.getKey(), GlobalPermission.PROVISION_PROJECTS.getKey()); + } + + @Test + public void apply_default_template_on_application() { + ProjectDto application = dbTester.components().insertPublicApplication().getProjectDto(); + PermissionTemplateDto projectPermissionTemplate = dbTester.permissionTemplates().insertTemplate(); + PermissionTemplateDto appPermissionTemplate = dbTester.permissionTemplates().insertTemplate(); + GroupDto group = dbTester.users().insertGroup(); + dbTester.permissionTemplates().addGroupToTemplate(appPermissionTemplate, group, GlobalPermission.ADMINISTER.getKey()); + dbTester.permissionTemplates().addGroupToTemplate(appPermissionTemplate, group, GlobalPermission.PROVISION_PROJECTS.getKey()); + dbTester.permissionTemplates().setDefaultTemplates(projectPermissionTemplate, appPermissionTemplate, null); + + underTest.applyDefaultToNewComponent(session, application, null); + + assertThat(selectProjectPermissionsOfGroup(group, application.getUuid())) + .containsOnly(GlobalPermission.ADMINISTER.getKey(), GlobalPermission.PROVISION_PROJECTS.getKey()); + } + + @Test + public void apply_default_template_on_portfolio() { + PortfolioDto portfolio = dbTester.components().insertPublicPortfolioDto(); + PermissionTemplateDto projectPermissionTemplate = dbTester.permissionTemplates().insertTemplate(); + PermissionTemplateDto portPermissionTemplate = dbTester.permissionTemplates().insertTemplate(); + GroupDto group = dbTester.users().insertGroup(); + dbTester.permissionTemplates().addGroupToTemplate(portPermissionTemplate, group, GlobalPermission.ADMINISTER.getKey()); + dbTester.permissionTemplates().addGroupToTemplate(portPermissionTemplate, group, GlobalPermission.PROVISION_PROJECTS.getKey()); + dbTester.permissionTemplates().setDefaultTemplates(projectPermissionTemplate, null, portPermissionTemplate); + + underTest.applyDefaultToNewComponent(session, portfolio, null); + + assertThat(selectProjectPermissionsOfGroup(group, portfolio.getUuid())) + .containsOnly(GlobalPermission.ADMINISTER.getKey(), GlobalPermission.PROVISION_PROJECTS.getKey()); + } + + @Test + public void apply_project_default_template_on_view_when_no_view_default_template() { + PortfolioDto portfolio = dbTester.components().insertPrivatePortfolioDto(); + PermissionTemplateDto projectPermissionTemplate = dbTester.permissionTemplates().insertTemplate(); + GroupDto group = dbTester.users().insertGroup(); + dbTester.permissionTemplates().addGroupToTemplate(projectPermissionTemplate, group, GlobalPermission.PROVISION_PROJECTS.getKey()); + dbTester.permissionTemplates().setDefaultTemplates(projectPermissionTemplate, null, null); + + underTest.applyDefaultToNewComponent(session, portfolio, null); + + assertThat(selectProjectPermissionsOfGroup(group, portfolio.getUuid())).containsOnly(GlobalPermission.PROVISION_PROJECTS.getKey()); + } + + @Test + public void apply_template_on_applications() { + ProjectDto application = dbTester.components().insertPublicApplication().getProjectDto(); + PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); + GroupDto group = dbTester.users().insertGroup(); + dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, GlobalPermission.ADMINISTER.getKey()); + dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, GlobalPermission.PROVISION_PROJECTS.getKey()); + dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); + + underTest.applyDefaultToNewComponent(session, application, null); + + assertThat(selectProjectPermissionsOfGroup(group, application.getUuid())) + .containsOnly(GlobalPermission.ADMINISTER.getKey(), GlobalPermission.PROVISION_PROJECTS.getKey()); + } + + @Test + public void apply_default_view_template_on_application() { + ProjectDto application = dbTester.components().insertPublicApplication().getProjectDto(); + PermissionTemplateDto projectPermissionTemplate = dbTester.permissionTemplates().insertTemplate(); + PermissionTemplateDto appPermissionTemplate = dbTester.permissionTemplates().insertTemplate(); + PermissionTemplateDto portPermissionTemplate = dbTester.permissionTemplates().insertTemplate(); + GroupDto group = dbTester.users().insertGroup(); + dbTester.permissionTemplates().addGroupToTemplate(appPermissionTemplate, group, GlobalPermission.ADMINISTER.getKey()); + dbTester.permissionTemplates().addGroupToTemplate(appPermissionTemplate, group, GlobalPermission.PROVISION_PROJECTS.getKey()); + dbTester.permissionTemplates().setDefaultTemplates(projectPermissionTemplate, appPermissionTemplate, portPermissionTemplate); + + underTest.applyDefaultToNewComponent(session, application, null); + + assertThat(selectProjectPermissionsOfGroup(group, application.getUuid())) + .containsOnly(GlobalPermission.ADMINISTER.getKey(), GlobalPermission.PROVISION_PROJECTS.getKey()); + } + + @Test + public void apply_project_default_template_on_application_when_no_application_default_template() { + ProjectDto application = dbTester.components().insertPublicApplication().getProjectDto(); + PermissionTemplateDto projectPermissionTemplate = dbTester.permissionTemplates().insertTemplate(); + GroupDto group = dbTester.users().insertGroup(); + dbTester.permissionTemplates().addGroupToTemplate(projectPermissionTemplate, group, GlobalPermission.PROVISION_PROJECTS.getKey()); + dbTester.permissionTemplates().setDefaultTemplates(projectPermissionTemplate, null, null); + + underTest.applyDefaultToNewComponent(session, application, null); + + assertThat(selectProjectPermissionsOfGroup(group, application.getUuid())).containsOnly(GlobalPermission.PROVISION_PROJECTS.getKey()); + } + + @Test + public void apply_permission_template() { + UserDto user = dbTester.users().insertUser(); + ProjectDto project = dbTester.components().insertPrivateProject().getProjectDto(); + GroupDto adminGroup = dbTester.users().insertGroup(); + GroupDto userGroup = dbTester.users().insertGroup(); + dbTester.users().insertPermissionOnGroup(adminGroup, GlobalPermission.ADMINISTER.getKey()); + dbTester.users().insertPermissionOnGroup(userGroup, UserRole.USER); + dbTester.users().insertGlobalPermissionOnUser(user, GlobalPermission.ADMINISTER); + PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); + dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, adminGroup, GlobalPermission.ADMINISTER.getKey()); + dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, adminGroup, UserRole.ISSUE_ADMIN); + dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, userGroup, UserRole.USER); + dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, userGroup, UserRole.CODEVIEWER); + dbTester.permissionTemplates().addAnyoneToTemplate(permissionTemplate, UserRole.USER); + dbTester.permissionTemplates().addAnyoneToTemplate(permissionTemplate, UserRole.CODEVIEWER); + dbTester.permissionTemplates().addUserToTemplate(permissionTemplate, user, GlobalPermission.ADMINISTER.getKey()); + + assertThat(selectProjectPermissionsOfGroup(adminGroup, project.getUuid())).isEmpty(); + assertThat(selectProjectPermissionsOfGroup(userGroup, project.getUuid())).isEmpty(); + assertThat(selectProjectPermissionsOfGroup(null, project.getUuid())).isEmpty(); + assertThat(selectProjectPermissionsOfUser(user, project.getUuid())).isEmpty(); + + underTest.applyAndCommit(session, permissionTemplate, singletonList(project)); + + assertThat(selectProjectPermissionsOfGroup(adminGroup, project.getUuid())).containsOnly(GlobalPermission.ADMINISTER.getKey(), UserRole.ISSUE_ADMIN); + assertThat(selectProjectPermissionsOfGroup(userGroup, project.getUuid())).containsOnly(UserRole.USER, UserRole.CODEVIEWER); + assertThat(selectProjectPermissionsOfGroup(null, project.getUuid())).isEmpty(); + assertThat(selectProjectPermissionsOfUser(user, project.getUuid())).containsOnly(GlobalPermission.ADMINISTER.getKey()); + } + + private List selectProjectPermissionsOfGroup(@Nullable GroupDto groupDto, String projectUuid) { + return dbTester.getDbClient().groupPermissionDao().selectEntityPermissionsOfGroup(session, groupDto != null ? groupDto.getUuid() : null, projectUuid); + } + + private List selectProjectPermissionsOfUser(UserDto userDto, String projectUuid) { + return dbTester.getDbClient().userPermissionDao().selectEntityPermissionsOfUser(session, userDto.getUuid(), projectUuid); + } + + @Test + public void would_user_have_scan_permission_with_default_permission_template() { + GroupDto group = dbTester.users().insertGroup(); + UserDto user = dbTester.users().insertUser(); + dbTester.users().insertMember(group, user); + PermissionTemplateDto template = templateDb.insertTemplate(); + dbTester.permissionTemplates().setDefaultTemplates(template, null, null); + templateDb.addProjectCreatorToTemplate(template.getUuid(), GlobalPermission.SCAN.getKey(), template.getName()); + templateDb.addUserToTemplate(template.getUuid(), user.getUuid(), UserRole.USER, template.getName(), user.getLogin()); + templateDb.addGroupToTemplate(template.getUuid(), group.getUuid(), UserRole.CODEVIEWER, template.getName(), group.getName()); + templateDb.addGroupToTemplate(template.getUuid(), null, UserRole.ISSUE_ADMIN, template.getName(), null); + + // authenticated user + checkWouldUserHaveScanPermission(user.getUuid(), true); + + // anonymous user + checkWouldUserHaveScanPermission(null, false); + } + + @Test + public void would_user_have_scan_permission_with_unknown_default_permission_template() { + dbTester.permissionTemplates().setDefaultTemplates("UNKNOWN_TEMPLATE_UUID", null, null); + + checkWouldUserHaveScanPermission(null, false); + } + + @Test + public void would_user_have_scan_permission_with_empty_template() { + PermissionTemplateDto template = templateDb.insertTemplate(); + dbTester.permissionTemplates().setDefaultTemplates(template, null, null); + + checkWouldUserHaveScanPermission(null, false); + } + + @Test + public void apply_permission_template_with_key_pattern_collision() { + final String key = "hi-test"; + final String keyPattern = ".*-test"; + + Stream templates = Stream.of( + templateDb.insertTemplate(t -> t.setKeyPattern(keyPattern)), + templateDb.insertTemplate(t -> t.setKeyPattern(keyPattern)) + ); + + String templateNames = templates + .map(PermissionTemplateDto::getName) + .sorted(String.CASE_INSENSITIVE_ORDER) + .map(x -> String.format("\"%s\"", x)) + .collect(Collectors.joining(", ")); + + ProjectDto project = dbTester.components().insertPrivateProject(p -> p.setKey(key)).getProjectDto(); + + assertThatThrownBy(() -> underTest.applyDefaultToNewComponent(session, project, null)) + .isInstanceOf(TemplateMatchingKeyException.class) + .hasMessageContaining("The \"%s\" key matches multiple permission templates: %s.", key, templateNames); + } + + private void checkWouldUserHaveScanPermission(@Nullable String userUuid, boolean expectedResult) { + assertThat(underTest.wouldUserHaveScanPermissionWithDefaultTemplate(session, userUuid, "PROJECT_KEY")) + .isEqualTo(expectedResult); + } + +} diff --git a/server/sonar-webserver-common/src/it/java/org/sonar/server/common/permission/UserPermissionChangerIT.java b/server/sonar-webserver-common/src/it/java/org/sonar/server/common/permission/UserPermissionChangerIT.java new file mode 100644 index 00000000000..479a28e81d4 --- /dev/null +++ b/server/sonar-webserver-common/src/it/java/org/sonar/server/common/permission/UserPermissionChangerIT.java @@ -0,0 +1,346 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.permission; + +import java.util.Set; +import org.apache.commons.lang3.StringUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.ResourceTypes; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +import org.sonar.core.util.SequenceUuidFactory; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.component.ResourceTypesRule; +import org.sonar.db.entity.EntityDto; +import org.sonar.db.permission.GlobalPermission; +import org.sonar.db.user.GroupDto; +import org.sonar.db.user.UserDto; +import org.sonar.db.user.UserIdDto; +import org.sonar.server.exceptions.BadRequestException; +import org.sonar.server.permission.PermissionService; +import org.sonar.server.permission.PermissionServiceImpl; + +import static java.util.stream.Collectors.toSet; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.sonar.server.common.permission.Operation.ADD; +import static org.sonar.server.common.permission.Operation.REMOVE; +import static org.sonar.server.permission.PermissionServiceImpl.ALL_PROJECT_PERMISSIONS; + +public class UserPermissionChangerIT { + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + + private final ResourceTypes resourceTypes = new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT); + private final PermissionService permissionService = new PermissionServiceImpl(resourceTypes); + private final UserPermissionChanger underTest = new UserPermissionChanger(db.getDbClient(), new SequenceUuidFactory()); + private UserDto user1; + private UserDto user2; + private EntityDto privateProject; + private EntityDto publicProject; + + @Before + public void setUp() { + user1 = db.users().insertUser(); + user2 = db.users().insertUser(); + privateProject = db.components().insertPrivateProject().getProjectDto(); + publicProject = db.components().insertPublicProject().getProjectDto(); + } + + @Test + public void apply_adds_any_global_permission_to_user() { + permissionService.getGlobalPermissions() + .forEach(perm -> { + UserPermissionChange change = new UserPermissionChange(ADD, perm.getKey(), null, UserIdDto.from(user1), permissionService); + + apply(change); + + assertThat(db.users().selectPermissionsOfUser(user1)).contains(perm); + }); + } + + @Test + public void apply_removes_any_global_permission_to_user() { + // give ADMIN perm to user2 so that user1 is not the only one with this permission and it can be removed from user1 + db.users().insertGlobalPermissionOnUser(user2, GlobalPermission.ADMINISTER); + permissionService.getGlobalPermissions() + .forEach(perm -> db.users().insertGlobalPermissionOnUser(user1, perm)); + assertThat(db.users().selectPermissionsOfUser(user1)) + .containsOnly(permissionService.getGlobalPermissions().toArray(new GlobalPermission[0])); + + permissionService.getGlobalPermissions() + .forEach(perm -> { + UserPermissionChange change = new UserPermissionChange(REMOVE, perm.getKey(), null, UserIdDto.from(user1), permissionService); + + apply(change, permissionService.getGlobalPermissions().stream().map(GlobalPermission::getKey).collect(toSet())); + + assertThat(db.users().selectPermissionsOfUser(user1)).doesNotContain(perm); + }); + } + + @Test + public void apply_has_no_effect_when_adding_permission_USER_on_a_public_project() { + UserPermissionChange change = new UserPermissionChange(ADD, UserRole.USER, publicProject, UserIdDto.from(user1), permissionService); + + apply(change); + + assertThat(db.users().selectEntityPermissionOfUser(user1, publicProject.getUuid())).doesNotContain(UserRole.USER); + } + + @Test + public void apply_has_no_effect_when_adding_permission_CODEVIEWER_on_a_public_project() { + UserPermissionChange change = new UserPermissionChange(ADD, UserRole.CODEVIEWER, publicProject, UserIdDto.from(user1), permissionService); + + apply(change); + + assertThat(db.users().selectEntityPermissionOfUser(user1, publicProject.getUuid())).doesNotContain(UserRole.CODEVIEWER); + } + + @Test + public void apply_adds_permission_ADMIN_on_a_public_project() { + applyAddsPermissionOnAPublicProject(UserRole.ADMIN); + } + + @Test + public void apply_adds_permission_ISSUE_ADMIN_on_a_public_project() { + applyAddsPermissionOnAPublicProject(UserRole.ISSUE_ADMIN); + } + + @Test + public void apply_adds_permission_SCAN_EXECUTION_on_a_public_project() { + applyAddsPermissionOnAPublicProject(GlobalPermission.SCAN.getKey()); + } + + private void applyAddsPermissionOnAPublicProject(String permission) { + UserPermissionChange change = new UserPermissionChange(ADD, permission, publicProject, UserIdDto.from(user1), permissionService); + + apply(change); + + assertThat(db.users().selectEntityPermissionOfUser(user1, publicProject.getUuid())).containsOnly(permission); + } + + @Test + public void apply_fails_with_BadRequestException_when_removing_permission_USER_from_a_public_project() { + UserPermissionChange change = new UserPermissionChange(REMOVE, UserRole.USER, publicProject, UserIdDto.from(user1), permissionService); + + assertThatThrownBy(() -> apply(change)) + .isInstanceOf(BadRequestException.class) + .hasMessage("Permission user can't be removed from a public component"); + } + + @Test + public void apply_fails_with_BadRequestException_when_removing_permission_CODEVIEWER_from_a_public_project() { + UserPermissionChange change = new UserPermissionChange(REMOVE, UserRole.CODEVIEWER, publicProject, UserIdDto.from(user1), permissionService); + + assertThatThrownBy(() -> apply(change)) + .isInstanceOf(BadRequestException.class) + .hasMessage("Permission codeviewer can't be removed from a public component"); + } + + @Test + public void apply_removes_permission_ADMIN_from_a_public_project() { + applyRemovesPermissionFromPublicProject(UserRole.ADMIN); + } + + @Test + public void apply_removes_permission_ISSUE_ADMIN_from_a_public_project() { + applyRemovesPermissionFromPublicProject(UserRole.ISSUE_ADMIN); + } + + @Test + public void apply_removes_permission_SCAN_EXECUTION_from_a_public_project() { + applyRemovesPermissionFromPublicProject(GlobalPermission.SCAN.getKey()); + } + + private void applyRemovesPermissionFromPublicProject(String permission) { + db.users().insertProjectPermissionOnUser(user1, permission, publicProject); + UserPermissionChange change = new UserPermissionChange(REMOVE, permission, publicProject, UserIdDto.from(user1), permissionService); + + apply(change, Set.of(permission)); + + assertThat(db.users().selectEntityPermissionOfUser(user1, publicProject.getUuid())).isEmpty(); + } + + @Test + public void apply_adds_any_permission_to_a_private_project() { + permissionService.getAllProjectPermissions() + .forEach(permission -> { + UserPermissionChange change = new UserPermissionChange(ADD, permission, privateProject, UserIdDto.from(user1), permissionService); + + apply(change); + + assertThat(db.users().selectEntityPermissionOfUser(user1, privateProject.getUuid())).contains(permission); + }); + } + + @Test + public void apply_removes_any_permission_from_a_private_project() { + permissionService.getAllProjectPermissions() + .forEach(permission -> db.users().insertProjectPermissionOnUser(user1, permission, privateProject)); + + permissionService.getAllProjectPermissions() + .forEach(permission -> { + UserPermissionChange change = new UserPermissionChange(REMOVE, permission, privateProject, UserIdDto.from(user1), permissionService); + + apply(change, ALL_PROJECT_PERMISSIONS); + + assertThat(db.users().selectEntityPermissionOfUser(user1, privateProject.getUuid())).doesNotContain(permission); + }); + } + + @Test + public void add_global_permission_to_user() { + UserPermissionChange change = new UserPermissionChange(ADD, GlobalPermission.SCAN.getKey(), null, UserIdDto.from(user1), permissionService); + + apply(change); + + assertThat(db.users().selectPermissionsOfUser(user1)).containsOnly(GlobalPermission.SCAN); + assertThat(db.users().selectEntityPermissionOfUser(user1, privateProject.getUuid())).isEmpty(); + assertThat(db.users().selectPermissionsOfUser(user2)).isEmpty(); + assertThat(db.users().selectEntityPermissionOfUser(user2, privateProject.getUuid())).isEmpty(); + } + + @Test + public void add_project_permission_to_user() { + UserPermissionChange change = new UserPermissionChange(ADD, UserRole.ISSUE_ADMIN, privateProject, UserIdDto.from(user1), permissionService); + apply(change); + + assertThat(db.users().selectPermissionsOfUser(user1)).isEmpty(); + assertThat(db.users().selectEntityPermissionOfUser(user1, privateProject.getUuid())).contains(UserRole.ISSUE_ADMIN); + assertThat(db.users().selectPermissionsOfUser(user2)).isEmpty(); + assertThat(db.users().selectEntityPermissionOfUser(user2, privateProject.getUuid())).isEmpty(); + } + + @Test + public void do_nothing_when_adding_global_permission_that_already_exists() { + db.users().insertGlobalPermissionOnUser(user1, GlobalPermission.ADMINISTER_QUALITY_GATES); + + UserPermissionChange change = new UserPermissionChange(ADD, GlobalPermission.ADMINISTER_QUALITY_GATES.getKey(), null, UserIdDto.from(user1), permissionService); + apply(change); + + assertThat(db.users().selectPermissionsOfUser(user1)).containsOnly(GlobalPermission.ADMINISTER_QUALITY_GATES); + } + + @Test + public void fail_to_add_global_permission_on_project() { + assertThatThrownBy(() -> { + UserPermissionChange change = new UserPermissionChange(ADD, GlobalPermission.ADMINISTER_QUALITY_GATES.getKey(), privateProject, UserIdDto.from(user1), permissionService); + apply(change); + }) + .isInstanceOf(BadRequestException.class) + .hasMessage("Invalid project permission 'gateadmin'. Valid values are [" + StringUtils.join(permissionService.getAllProjectPermissions(), ", ") + "]"); + } + + @Test + public void fail_to_add_project_permission() { + assertThatThrownBy(() -> { + UserPermissionChange change = new UserPermissionChange(ADD, UserRole.ISSUE_ADMIN, null, UserIdDto.from(user1), permissionService); + apply(change); + }) + .isInstanceOf(BadRequestException.class) + .hasMessage("Invalid global permission 'issueadmin'. Valid values are [admin, gateadmin, profileadmin, provisioning, scan]"); + } + + @Test + public void remove_global_permission_from_user() { + db.users().insertGlobalPermissionOnUser(user1, GlobalPermission.ADMINISTER_QUALITY_GATES); + db.users().insertGlobalPermissionOnUser(user1, GlobalPermission.SCAN); + db.users().insertGlobalPermissionOnUser(user2, GlobalPermission.ADMINISTER_QUALITY_GATES); + db.users().insertProjectPermissionOnUser(user1, UserRole.ISSUE_ADMIN, privateProject); + + UserPermissionChange change = new UserPermissionChange(REMOVE, GlobalPermission.ADMINISTER_QUALITY_GATES.getKey(), null, UserIdDto.from(user1), permissionService); + apply(change, Set.of(GlobalPermission.ADMINISTER_QUALITY_GATES.getKey(), GlobalPermission.SCAN.getKey(), UserRole.ISSUE_ADMIN)); + + assertThat(db.users().selectPermissionsOfUser(user1)).containsOnly(GlobalPermission.SCAN); + assertThat(db.users().selectPermissionsOfUser(user2)).containsOnly(GlobalPermission.ADMINISTER_QUALITY_GATES); + assertThat(db.users().selectEntityPermissionOfUser(user1, privateProject.getUuid())).containsOnly(UserRole.ISSUE_ADMIN); + } + + @Test + public void remove_project_permission_from_user() { + EntityDto project2 = db.components().insertPrivateProject().getProjectDto(); + db.users().insertGlobalPermissionOnUser(user1, GlobalPermission.ADMINISTER_QUALITY_GATES); + db.users().insertProjectPermissionOnUser(user1, UserRole.ISSUE_ADMIN, privateProject); + db.users().insertProjectPermissionOnUser(user1, UserRole.USER, privateProject); + db.users().insertProjectPermissionOnUser(user2, UserRole.ISSUE_ADMIN, privateProject); + db.users().insertProjectPermissionOnUser(user1, UserRole.ISSUE_ADMIN, project2); + + UserPermissionChange change = new UserPermissionChange(REMOVE, UserRole.ISSUE_ADMIN, privateProject, UserIdDto.from(user1), permissionService); + apply(change, Set.of(GlobalPermission.ADMINISTER_QUALITY_GATES.getKey(), UserRole.ISSUE_ADMIN, UserRole.USER)); + + assertThat(db.users().selectEntityPermissionOfUser(user1, privateProject.getUuid())).containsOnly(UserRole.USER); + assertThat(db.users().selectEntityPermissionOfUser(user2, privateProject.getUuid())).containsOnly(UserRole.ISSUE_ADMIN); + assertThat(db.users().selectEntityPermissionOfUser(user1, project2.getUuid())).containsOnly(UserRole.ISSUE_ADMIN); + } + + @Test + public void do_not_fail_if_removing_a_global_permission_that_does_not_exist() { + UserPermissionChange change = new UserPermissionChange(REMOVE, GlobalPermission.ADMINISTER_QUALITY_GATES.getKey(), null, UserIdDto.from(user1), permissionService); + apply(change); + + assertThat(db.users().selectPermissionsOfUser(user1)).isEmpty(); + } + + @Test + public void do_not_fail_if_removing_a_project_permission_that_does_not_exist() { + UserPermissionChange change = new UserPermissionChange(REMOVE, UserRole.ISSUE_ADMIN, privateProject, UserIdDto.from(user1), permissionService); + apply(change); + + assertThat(db.users().selectEntityPermissionOfUser(user1, privateProject.getUuid())).isEmpty(); + } + + @Test + public void fail_to_remove_admin_global_permission_if_no_more_admins() { + db.users().insertGlobalPermissionOnUser(user1, GlobalPermission.ADMINISTER); + + UserPermissionChange change = new UserPermissionChange(REMOVE, GlobalPermission.ADMINISTER.getKey(), null, UserIdDto.from(user1), permissionService); + DbSession session = db.getSession(); + Set permissions = Set.of(GlobalPermission.ADMINISTER.getKey()); + assertThatThrownBy(() -> underTest.apply(session, permissions, change)) + .isInstanceOf(BadRequestException.class) + .hasMessage("Last user with permission 'admin'. Permission cannot be removed."); + } + + @Test + public void remove_admin_user_if_still_other_admins() { + db.users().insertGlobalPermissionOnUser(user1, GlobalPermission.ADMINISTER); + GroupDto admins = db.users().insertGroup("admins"); + db.users().insertMember(admins, user2); + db.users().insertPermissionOnGroup(admins, GlobalPermission.ADMINISTER); + + UserPermissionChange change = new UserPermissionChange(REMOVE, GlobalPermission.ADMINISTER.getKey(), null, UserIdDto.from(user1), permissionService); + underTest.apply(db.getSession(), Set.of(GlobalPermission.ADMINISTER.getKey()), change); + + assertThat(db.users().selectPermissionsOfUser(user1)).isEmpty(); + } + + private void apply(UserPermissionChange change) { + underTest.apply(db.getSession(), Set.of(), change); + db.commit(); + } + private void apply(UserPermissionChange change, Set existingPermissions) { + underTest.apply(db.getSession(), existingPermissions, change); + db.commit(); + } +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almintegration/ProjectKeyGenerator.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almintegration/ProjectKeyGenerator.java new file mode 100644 index 00000000000..cf5a04a2c8e --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almintegration/ProjectKeyGenerator.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almintegration; + +import com.google.common.annotations.VisibleForTesting; +import java.util.List; +import org.apache.commons.lang3.StringUtils; +import org.sonar.core.util.UuidFactory; + +import static com.google.common.collect.Lists.asList; +import static org.sonar.core.component.ComponentKeys.sanitizeProjectKey; + +public class ProjectKeyGenerator { + + @VisibleForTesting + static final int MAX_PROJECT_KEY_SIZE = 250; + @VisibleForTesting + static final Character PROJECT_KEY_SEPARATOR = '_'; + + private final UuidFactory uuidFactory; + + public ProjectKeyGenerator(UuidFactory uuidFactory) { + this.uuidFactory = uuidFactory; + } + + public String generateUniqueProjectKey(String projectName, String... extraProjectKeyItems) { + String sqProjectKey = generateCompleteProjectKey(projectName, extraProjectKeyItems); + sqProjectKey = truncateProjectKeyIfNecessary(sqProjectKey); + return sanitizeProjectKey(sqProjectKey); + } + + private String generateCompleteProjectKey(String projectName, String[] extraProjectKeyItems) { + List projectKeyItems = asList(projectName, extraProjectKeyItems); + String projectKey = StringUtils.join(projectKeyItems, PROJECT_KEY_SEPARATOR); + String uuid = uuidFactory.create(); + return projectKey + PROJECT_KEY_SEPARATOR + uuid; + } + + private static String truncateProjectKeyIfNecessary(String sqProjectKey) { + if (sqProjectKey.length() > MAX_PROJECT_KEY_SIZE) { + return sqProjectKey.substring(sqProjectKey.length() - MAX_PROJECT_KEY_SIZE); + } + return sqProjectKey; + } + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almintegration/package-info.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almintegration/package-info.java new file mode 100644 index 00000000000..6cfcd9bc704 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almintegration/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.common.almintegration; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DelegatingDevOpsProjectCreatorFactory.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DelegatingDevOpsProjectCreatorFactory.java new file mode 100644 index 00000000000..dc0660d97cf --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DelegatingDevOpsProjectCreatorFactory.java @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import javax.annotation.Priority; +import org.sonar.api.server.ServerSide; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; + +@ServerSide +@Priority(1) +public class DelegatingDevOpsProjectCreatorFactory implements DevOpsProjectCreatorFactory { + + private final Set delegates; + + public DelegatingDevOpsProjectCreatorFactory(Set delegates) { + this.delegates = delegates; + } + + @Override + public Optional getDevOpsProjectCreator(DbSession dbSession, Map characteristics) { + return delegates.stream() + .flatMap(delegate -> delegate.getDevOpsProjectCreator(dbSession, characteristics).stream()) + .findFirst(); + } + + @Override + public Optional getDevOpsProjectCreator(AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor) { + return delegates.stream() + .flatMap(delegate -> delegate.getDevOpsProjectCreator(almSettingDto, devOpsProjectDescriptor).stream()) + .findFirst(); + } + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectCreator.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectCreator.java new file mode 100644 index 00000000000..86225fa1ac8 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectCreator.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings; + +import javax.annotation.Nullable; +import org.sonar.db.DbSession; +import org.sonar.db.project.CreationMethod; +import org.sonar.server.component.ComponentCreationData; + +public interface DevOpsProjectCreator { + + boolean isScanAllowedUsingPermissionsFromDevopsPlatform(); + + ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, CreationMethod creationMethod, Boolean monorepo, @Nullable String projectKey, + @Nullable String projectName); + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectCreatorFactory.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectCreatorFactory.java new file mode 100644 index 00000000000..55835f45c23 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectCreatorFactory.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings; + +import java.util.Map; +import java.util.Optional; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; + +public interface DevOpsProjectCreatorFactory { + + Optional getDevOpsProjectCreator(DbSession dbSession, Map characteristics); + + Optional getDevOpsProjectCreator(AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor); + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectDescriptor.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectDescriptor.java new file mode 100644 index 00000000000..4459ccbbf09 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectDescriptor.java @@ -0,0 +1,25 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings; + +import org.sonar.db.alm.setting.ALM; + +public record DevOpsProjectDescriptor(ALM alm, String url, String projectIdentifier) { +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreationParameters.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreationParameters.java new file mode 100644 index 00000000000..72b2ca87602 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreationParameters.java @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings.github; + +import javax.annotation.Nullable; +import org.sonar.auth.github.AppInstallationToken; +import org.sonar.auth.github.security.AccessToken; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.user.UserSession; + +public record GithubProjectCreationParameters(DevOpsProjectDescriptor devOpsProjectDescriptor, AlmSettingDto almSettingDto, UserSession userSession, + AccessToken devOpsAppInstallationToken, + @Nullable AppInstallationToken authAppInstallationToken) { +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreator.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreator.java new file mode 100644 index 00000000000..47902dd9c98 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreator.java @@ -0,0 +1,229 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings.github; + +import java.util.Optional; +import java.util.Set; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.sonar.alm.client.github.GithubPermissionConverter; +import org.sonar.api.web.UserRole; +import org.sonar.auth.github.AppInstallationToken; +import org.sonar.auth.github.GitHubSettings; +import org.sonar.auth.github.GsonRepositoryCollaborator; +import org.sonar.auth.github.GsonRepositoryPermissions; +import org.sonar.auth.github.GsonRepositoryTeam; +import org.sonar.auth.github.client.GithubApplicationClient; +import org.sonar.auth.github.client.GithubApplicationClient.Repository; +import org.sonar.auth.github.security.AccessToken; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; +import org.sonar.db.component.BranchDto; +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.db.user.UserIdDto; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.almsettings.DevOpsProjectCreator; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.common.permission.Operation; +import org.sonar.server.common.permission.PermissionUpdater; +import org.sonar.server.common.permission.UserPermissionChange; +import org.sonar.server.common.project.ProjectCreator; +import org.sonar.server.component.ComponentCreationData; +import org.sonar.server.management.ManagedProjectService; +import org.sonar.server.permission.PermissionService; +import org.sonar.server.user.UserSession; + +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toSet; +import static org.sonar.api.utils.Preconditions.checkState; + +public class GithubProjectCreator implements DevOpsProjectCreator { + + private final DbClient dbClient; + private final GithubApplicationClient githubApplicationClient; + private final GithubPermissionConverter githubPermissionConverter; + private final ProjectKeyGenerator projectKeyGenerator; + private final PermissionUpdater permissionUpdater; + private final PermissionService permissionService; + private final ManagedProjectService managedProjectService; + private final ProjectCreator projectCreator; + private final GithubProjectCreationParameters githubProjectCreationParameters; + private final DevOpsProjectDescriptor devOpsProjectDescriptor; + private final UserSession userSession; + private final AlmSettingDto almSettingDto; + private final AccessToken devOpsAppInstallationToken; + private final GitHubSettings gitHubSettings; + + @CheckForNull + private final AppInstallationToken authAppInstallationToken; + + public GithubProjectCreator(DbClient dbClient, GithubApplicationClient githubApplicationClient, GithubPermissionConverter githubPermissionConverter, + ProjectKeyGenerator projectKeyGenerator, PermissionUpdater permissionUpdater, PermissionService permissionService, + ManagedProjectService managedProjectService, ProjectCreator projectCreator, GithubProjectCreationParameters githubProjectCreationParameters, GitHubSettings gitHubSettings) { + + this.dbClient = dbClient; + this.githubApplicationClient = githubApplicationClient; + this.githubPermissionConverter = githubPermissionConverter; + this.projectKeyGenerator = projectKeyGenerator; + this.permissionUpdater = permissionUpdater; + this.permissionService = permissionService; + this.managedProjectService = managedProjectService; + this.projectCreator = projectCreator; + this.githubProjectCreationParameters = githubProjectCreationParameters; + userSession = githubProjectCreationParameters.userSession(); + almSettingDto = githubProjectCreationParameters.almSettingDto(); + devOpsProjectDescriptor = githubProjectCreationParameters.devOpsProjectDescriptor(); + devOpsAppInstallationToken = githubProjectCreationParameters.devOpsAppInstallationToken(); + authAppInstallationToken = githubProjectCreationParameters.authAppInstallationToken(); + this.gitHubSettings = gitHubSettings; + } + + @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]; + + Set permissionsMappingDtos = dbClient.githubPermissionsMappingDao().findAll(dbClient.openSession(false)); + + boolean userHasDirectAccessToRepo = doesUserHaveScanPermission(organization, repository, permissionsMappingDtos); + if (userHasDirectAccessToRepo) { + return true; + } + return doesUserBelongToAGroupWithScanPermission(organization, repository, permissionsMappingDtos); + } + + private boolean doesUserHaveScanPermission(String organization, String repository, Set permissionsMappingDtos) { + Set repositoryCollaborators = githubApplicationClient.getRepositoryCollaborators(devOpsProjectDescriptor.url(), authAppInstallationToken, + organization, repository); + + String externalLogin = userSession.getExternalIdentity().map(UserSession.ExternalIdentity::login).orElse(null); + if (externalLogin == null) { + return false; + } + return repositoryCollaborators.stream() + .filter(gsonRepositoryCollaborator -> externalLogin.equals(gsonRepositoryCollaborator.name())) + .findAny() + .map(gsonRepositoryCollaborator -> hasScanPermission(permissionsMappingDtos, gsonRepositoryCollaborator.roleName(), gsonRepositoryCollaborator.permissions())) + .orElse(false); + } + + private boolean doesUserBelongToAGroupWithScanPermission(String organization, String repository, + Set permissionsMappingDtos) { + Set repositoryTeams = githubApplicationClient.getRepositoryTeams(devOpsProjectDescriptor.url(), authAppInstallationToken, organization, repository); + + Set groupsOfUser = findUserMembershipOnSonarQube(organization); + return repositoryTeams.stream() + .filter(team -> hasScanPermission(permissionsMappingDtos, team.permission(), team.permissions())) + .map(GsonRepositoryTeam::name) + .anyMatch(groupsOfUser::contains); + } + + private Set findUserMembershipOnSonarQube(String organization) { + return userSession.getGroups().stream() + .map(GroupDto::getName) + .filter(groupName -> groupName.contains("/")) + .map(name -> name.replaceFirst(organization + "/", "")) + .collect(toSet()); + } + + private boolean hasScanPermission(Set permissionsMappingDtos, String role, GsonRepositoryPermissions permissions) { + Set sonarqubePermissions = githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(permissionsMappingDtos, + role, permissions); + return sonarqubePermissions.contains(UserRole.SCAN); + } + + @Override + public ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, CreationMethod creationMethod, Boolean monorepo, @Nullable String projectKey, + @Nullable String projectName) { + String url = requireNonNull(almSettingDto.getUrl(), "DevOps Platform url cannot be null"); + 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()))); + + return createProjectAndBindToDevOpsPlatform(dbSession, monorepo, projectKey, projectName, almSettingDto, repository, creationMethod); + } + + private ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, Boolean monorepo, @Nullable String projectKey, @Nullable String projectName, + AlmSettingDto almSettingDto, + Repository repository, CreationMethod creationMethod) { + String key = Optional.ofNullable(projectKey).orElse(getUniqueProjectKey(repository)); + + boolean isManaged = gitHubSettings.isProvisioningEnabled(); + + ComponentCreationData componentCreationData = projectCreator.createProject(dbSession, key, Optional.ofNullable(projectName).orElse(repository.getName()), + repository.getDefaultBranch(), creationMethod, + shouldProjectBePrivate(repository), isManaged); + ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow(); + createProjectAlmSettingDto(dbSession, repository, projectDto, almSettingDto, monorepo); + addScanPermissionToCurrentUser(dbSession, projectDto); + + BranchDto mainBranchDto = Optional.ofNullable(componentCreationData.mainBranchDto()).orElseThrow(); + if (gitHubSettings.isProvisioningEnabled()) { + syncProjectPermissionsWithGithub(projectDto, mainBranchDto); + } + return componentCreationData; + } + + @CheckForNull + private Boolean shouldProjectBePrivate(Repository repository) { + if (gitHubSettings.isProvisioningEnabled() && gitHubSettings.isProjectVisibilitySynchronizationActivated()) { + return repository.isPrivate(); + } else if (gitHubSettings.isProvisioningEnabled()) { + return true; + } else { + return null; + } + } + + private void addScanPermissionToCurrentUser(DbSession dbSession, ProjectDto projectDto) { + UserIdDto userId = new UserIdDto(requireNonNull(userSession.getUuid()), requireNonNull(userSession.getLogin())); + UserPermissionChange scanPermission = new UserPermissionChange(Operation.ADD, UserRole.SCAN, projectDto, userId, permissionService); + permissionUpdater.apply(dbSession, Set.of(scanPermission)); + } + + private void syncProjectPermissionsWithGithub(ProjectDto projectDto, BranchDto mainBranchDto) { + String userUuid = requireNonNull(userSession.getUuid()); + managedProjectService.queuePermissionSyncTask(userUuid, mainBranchDto.getUuid(), projectDto.getUuid()); + } + + private String getUniqueProjectKey(Repository repository) { + return projectKeyGenerator.generateUniqueProjectKey(repository.getFullName()); + } + + private void createProjectAlmSettingDto(DbSession dbSession, Repository repo, ProjectDto projectDto, AlmSettingDto almSettingDto, Boolean monorepo) { + ProjectAlmSettingDto projectAlmSettingDto = new ProjectAlmSettingDto() + .setAlmSettingUuid(almSettingDto.getUuid()) + .setAlmRepo(repo.getFullName()) + .setAlmSlug(null) + .setProjectUuid(projectDto.getUuid()) + .setSummaryCommentEnabled(true) + .setMonorepo(monorepo); + dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, projectAlmSettingDto, almSettingDto.getKey(), projectDto.getName(), projectDto.getKey()); + } + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorFactory.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorFactory.java new file mode 100644 index 00000000000..2a1821c2165 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorFactory.java @@ -0,0 +1,174 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings.github; + +import java.util.Map; +import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.alm.client.github.GithubGlobalSettingsValidator; +import org.sonar.alm.client.github.GithubPermissionConverter; +import org.sonar.api.server.ServerSide; +import org.sonar.auth.github.AppInstallationToken; +import org.sonar.auth.github.GitHubSettings; +import org.sonar.auth.github.GithubAppConfiguration; +import org.sonar.auth.github.client.GithubApplicationClient; +import org.sonar.auth.github.security.AccessToken; +import org.sonar.auth.github.security.UserAccessToken; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.pat.AlmPatDto; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.almsettings.DevOpsProjectCreator; +import org.sonar.server.common.almsettings.DevOpsProjectCreatorFactory; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.common.permission.PermissionUpdater; +import org.sonar.server.common.permission.UserPermissionChange; +import org.sonar.server.common.project.ProjectCreator; +import org.sonar.server.exceptions.BadConfigurationException; +import org.sonar.server.management.ManagedProjectService; +import org.sonar.server.permission.PermissionService; +import org.sonar.server.user.UserSession; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; +import static org.sonar.core.ce.CeTaskCharacteristics.DEVOPS_PLATFORM_PROJECT_IDENTIFIER; +import static org.sonar.core.ce.CeTaskCharacteristics.DEVOPS_PLATFORM_URL; + +@ServerSide +public class GithubProjectCreatorFactory implements DevOpsProjectCreatorFactory { + private static final Logger LOG = LoggerFactory.getLogger(GithubProjectCreatorFactory.class); + + private final DbClient dbClient; + private final GithubGlobalSettingsValidator githubGlobalSettingsValidator; + private final GithubApplicationClient githubApplicationClient; + private final ProjectKeyGenerator projectKeyGenerator; + private final ProjectCreator projectCreator; + private final UserSession userSession; + private final GitHubSettings gitHubSettings; + private final GithubPermissionConverter githubPermissionConverter; + private final PermissionUpdater permissionUpdater; + private final PermissionService permissionService; + private final ManagedProjectService managedProjectService; + + public GithubProjectCreatorFactory(DbClient dbClient, GithubGlobalSettingsValidator githubGlobalSettingsValidator, + GithubApplicationClient githubApplicationClient, ProjectKeyGenerator projectKeyGenerator, UserSession userSession, + ProjectCreator projectCreator, GitHubSettings gitHubSettings, GithubPermissionConverter githubPermissionConverter, + PermissionUpdater permissionUpdater, PermissionService permissionService, ManagedProjectService managedProjectService) { + this.dbClient = dbClient; + this.githubGlobalSettingsValidator = githubGlobalSettingsValidator; + this.githubApplicationClient = githubApplicationClient; + this.projectKeyGenerator = projectKeyGenerator; + this.userSession = userSession; + this.projectCreator = projectCreator; + this.gitHubSettings = gitHubSettings; + this.githubPermissionConverter = githubPermissionConverter; + this.permissionUpdater = permissionUpdater; + this.permissionService = permissionService; + this.managedProjectService = managedProjectService; + } + + @Override + public Optional getDevOpsProjectCreator(DbSession dbSession, Map characteristics) { + String githubApiUrl = characteristics.get(DEVOPS_PLATFORM_URL); + String githubRepository = characteristics.get(DEVOPS_PLATFORM_PROJECT_IDENTIFIER); + if (githubApiUrl == null || githubRepository == null) { + return Optional.empty(); + } + DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, githubApiUrl, githubRepository); + + return dbClient.almSettingDao().selectByAlm(dbSession, ALM.GITHUB).stream() + .filter(almSettingDto -> devOpsProjectDescriptor.url().equals(almSettingDto.getUrl())) + .map(almSettingDto -> findInstallationIdAndCreateDevOpsProjectCreator(devOpsProjectDescriptor, almSettingDto)) + .flatMap(Optional::stream) + .findFirst(); + + } + + private Optional findInstallationIdAndCreateDevOpsProjectCreator(DevOpsProjectDescriptor devOpsProjectDescriptor, + AlmSettingDto almSettingDto) { + GithubAppConfiguration githubAppConfiguration = githubGlobalSettingsValidator.validate(almSettingDto); + return findInstallationIdToAccessRepo(githubAppConfiguration, devOpsProjectDescriptor.projectIdentifier()) + .map(installationId -> generateAppInstallationToken(githubAppConfiguration, installationId)) + .map(appInstallationToken -> createGithubProjectCreator(devOpsProjectDescriptor, almSettingDto, appInstallationToken)); + } + + private GithubProjectCreator createGithubProjectCreator(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, userSession, appInstallationToken, + authAppInstallationToken.orElse(null)); + return new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, permissionUpdater, permissionService, + managedProjectService, projectCreator, githubProjectCreationParameters, gitHubSettings); + } + + @Override + public Optional getDevOpsProjectCreator(AlmSettingDto almSettingDto, + DevOpsProjectDescriptor devOpsProjectDescriptor) { + if (almSettingDto.getAlm() != ALM.GITHUB) { + return Optional.empty(); + } + try (DbSession dbSession = dbClient.openSession(false)) { + AccessToken accessToken = getAccessToken(dbSession, almSettingDto); + Optional authAppInstallationToken = getAuthAppInstallationTokenIfNecessary(devOpsProjectDescriptor); + GithubProjectCreationParameters githubProjectCreationParameters = new GithubProjectCreationParameters(devOpsProjectDescriptor, almSettingDto, userSession, accessToken, + authAppInstallationToken.orElse(null)); + GithubProjectCreator githubProjectCreator = new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, permissionUpdater, + permissionService, managedProjectService, this.projectCreator, githubProjectCreationParameters, gitHubSettings); + return Optional.of(githubProjectCreator); + } + } + + private AccessToken getAccessToken(DbSession dbSession, AlmSettingDto almSettingDto) { + String userUuid = requireNonNull(userSession.getUuid(), "User UUID cannot be null."); + return dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSettingDto) + .map(AlmPatDto::getPersonalAccessToken) + .map(UserAccessToken::new) + .orElseThrow(() -> new IllegalArgumentException("No personal access token found")); + } + + 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 BadConfigurationException("PROJECT", + 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 created.", + 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-common/src/main/java/org/sonar/server/common/almsettings/github/package-info.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/package-info.java new file mode 100644 index 00000000000..651c6710b55 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.common.almsettings.github; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreator.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreator.java new file mode 100644 index 00000000000..2c530a0fc89 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreator.java @@ -0,0 +1,145 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings.gitlab; + +import java.util.Optional; +import org.jetbrains.annotations.Nullable; +import org.sonar.alm.client.gitlab.GitLabBranch; +import org.sonar.alm.client.gitlab.GitlabApplicationClient; +import org.sonar.alm.client.gitlab.GitlabServerException; +import org.sonar.alm.client.gitlab.Project; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.pat.AlmPatDto; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; +import org.sonar.db.project.CreationMethod; +import org.sonar.db.project.ProjectDto; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.almsettings.DevOpsProjectCreator; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.common.project.ProjectCreator; +import org.sonar.server.component.ComponentCreationData; +import org.sonar.server.user.UserSession; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +public class GitlabProjectCreator implements DevOpsProjectCreator { + + private final DbClient dbClient; + private final ProjectKeyGenerator projectKeyGenerator; + private final ProjectCreator projectCreator; + private final AlmSettingDto almSettingDto; + private final DevOpsProjectDescriptor devOpsProjectDescriptor; + private final GitlabApplicationClient gitlabApplicationClient; + private final UserSession userSession; + + public GitlabProjectCreator(DbClient dbClient, ProjectKeyGenerator projectKeyGenerator, ProjectCreator projectCreator, AlmSettingDto almSettingDto, + DevOpsProjectDescriptor devOpsProjectDescriptor, GitlabApplicationClient gitlabApplicationClient, UserSession userSession) { + this.dbClient = dbClient; + this.projectKeyGenerator = projectKeyGenerator; + this.projectCreator = projectCreator; + this.almSettingDto = almSettingDto; + this.devOpsProjectDescriptor = devOpsProjectDescriptor; + this.gitlabApplicationClient = gitlabApplicationClient; + this.userSession = userSession; + } + + @Override + public boolean isScanAllowedUsingPermissionsFromDevopsPlatform() { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, CreationMethod creationMethod, Boolean monorepo, @Nullable String projectKey, + @Nullable String projectName) { + + String pat = findPersonalAccessTokenOrThrow(dbSession, almSettingDto); + + String gitlabUrl = requireNonNull(almSettingDto.getUrl(), "DevOps Platform gitlabUrl cannot be null"); + + Long gitlabProjectId = getGitlabProjectId(); + Project gitlabProject = fetchGitlabProject(gitlabUrl, pat, gitlabProjectId); + + Optional almDefaultBranch = getDefaultBranchOnGitlab(gitlabUrl, pat, gitlabProjectId); + ComponentCreationData componentCreationData = projectCreator.createProject( + dbSession, + getProjectKey(projectKey, gitlabProject), + getProjectName(projectName, gitlabProject), + almDefaultBranch.orElse(null), + creationMethod); + ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow(); + + createProjectAlmSettingDto(dbSession, gitlabProjectId.toString(), projectDto, almSettingDto, monorepo); + return componentCreationData; + } + + private String findPersonalAccessTokenOrThrow(DbSession dbSession, AlmSettingDto almSettingDto) { + String userUuid = requireNonNull(userSession.getUuid(), "User UUID cannot be null."); + Optional almPatDto = dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSettingDto); + return almPatDto.map(AlmPatDto::getPersonalAccessToken) + .orElseThrow(() -> new IllegalArgumentException(format("personal access token for '%s' is missing", almSettingDto.getKey()))); + } + + private Long getGitlabProjectId() { + try { + return Long.parseLong(devOpsProjectDescriptor.projectIdentifier()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(format("GitLab project identifier must be a number, was '%s'", devOpsProjectDescriptor.projectIdentifier())); + } + } + + private Project fetchGitlabProject(String gitlabUrl, String pat, Long gitlabProjectId) { + try { + return gitlabApplicationClient.getProject( + gitlabUrl, + pat, + gitlabProjectId); + } catch (GitlabServerException e) { + throw new IllegalStateException(format("Failed to fetch GitLab project with ID '%s' from '%s'", gitlabProjectId, gitlabUrl), e); + } + } + + private Optional getDefaultBranchOnGitlab(String gitlabUrl, String pat, long gitlabProjectId) { + Optional almMainBranch = gitlabApplicationClient.getBranches(gitlabUrl, pat, gitlabProjectId).stream().filter(GitLabBranch::isDefault).findFirst(); + return almMainBranch.map(GitLabBranch::getName); + } + + private String getProjectKey(@Nullable String projectKey, Project gitlabProject) { + return Optional.ofNullable(projectKey).orElseGet(() -> projectKeyGenerator.generateUniqueProjectKey(gitlabProject.getPathWithNamespace())); + } + + private static String getProjectName(@Nullable String projectName, Project gitlabProject) { + return Optional.ofNullable(projectName).orElse(gitlabProject.getName()); + } + + private void createProjectAlmSettingDto(DbSession dbSession, String gitlabProjectId, ProjectDto projectDto, AlmSettingDto almSettingDto, Boolean monorepo) { + ProjectAlmSettingDto projectAlmSettingDto = new ProjectAlmSettingDto() + .setAlmSettingUuid(almSettingDto.getUuid()) + .setAlmRepo(gitlabProjectId) + .setAlmSlug(null) + .setProjectUuid(projectDto.getUuid()) + .setSummaryCommentEnabled(true) + .setMonorepo(monorepo); + dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, projectAlmSettingDto, almSettingDto.getKey(), projectDto.getName(), projectDto.getKey()); + } + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorFactory.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorFactory.java new file mode 100644 index 00000000000..19176e8c41a --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorFactory.java @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings.gitlab; + +import java.util.Map; +import java.util.Optional; +import org.sonar.alm.client.gitlab.GitlabApplicationClient; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.almsettings.DevOpsProjectCreator; +import org.sonar.server.common.almsettings.DevOpsProjectCreatorFactory; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.common.project.ProjectCreator; +import org.sonar.server.user.UserSession; + +public class GitlabProjectCreatorFactory implements DevOpsProjectCreatorFactory { + private final DbClient dbClient; + private final ProjectKeyGenerator projectKeyGenerator; + private final ProjectCreator projectCreator; + private final GitlabApplicationClient gitlabApplicationClient; + private final UserSession userSession; + + public GitlabProjectCreatorFactory(DbClient dbClient, ProjectKeyGenerator projectKeyGenerator, ProjectCreator projectCreator, GitlabApplicationClient gitlabApplicationClient, + UserSession userSession) { + this.dbClient = dbClient; + this.projectKeyGenerator = projectKeyGenerator; + this.projectCreator = projectCreator; + this.gitlabApplicationClient = gitlabApplicationClient; + this.userSession = userSession; + } + + @Override + public Optional getDevOpsProjectCreator(DbSession dbSession, Map characteristics) { + return Optional.empty(); + } + + @Override + public Optional getDevOpsProjectCreator(AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor) { + if (almSettingDto.getAlm() != ALM.GITLAB) { + return Optional.empty(); + } + return Optional.of( + new GitlabProjectCreator( + dbClient, + projectKeyGenerator, + projectCreator, + almSettingDto, + devOpsProjectDescriptor, + gitlabApplicationClient, + userSession)); + } +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/package-info.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/package-info.java new file mode 100644 index 00000000000..f5cc5a1abc8 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.common.almsettings.gitlab; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/package-info.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/package-info.java new file mode 100644 index 00000000000..2883960112a --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.common.almsettings; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/ComponentCreationParameters.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/ComponentCreationParameters.java new file mode 100644 index 00000000000..4ba5d2c8431 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/ComponentCreationParameters.java @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.component; + +import javax.annotation.Nullable; +import org.sonar.db.project.CreationMethod; + +public record ComponentCreationParameters(NewComponent newComponent, + @Nullable String userUuid, + @Nullable String userLogin, + @Nullable String mainBranchName, + boolean isManaged, + CreationMethod creationMethod) { + + public static ProjectCreationDataBuilder builder() { + return new ProjectCreationDataBuilder(); + } + + public static final class ProjectCreationDataBuilder { + private NewComponent newComponent; + private String userUuid = null; + private String userLogin = null; + private String mainBranchName = null; + private boolean isManaged = false; + private CreationMethod creationMethod; + + public ProjectCreationDataBuilder newComponent(NewComponent newComponent) { + this.newComponent = newComponent; + return this; + } + + public ProjectCreationDataBuilder userUuid(@Nullable String userUuid) { + this.userUuid = userUuid; + return this; + } + + public ProjectCreationDataBuilder userLogin(@Nullable String userLogin) { + this.userLogin = userLogin; + return this; + } + + public ProjectCreationDataBuilder mainBranchName(@Nullable String mainBranchName) { + this.mainBranchName = mainBranchName; + return this; + } + + public ProjectCreationDataBuilder isManaged(boolean isManaged) { + this.isManaged = isManaged; + return this; + } + + public ProjectCreationDataBuilder creationMethod(CreationMethod creationMethod) { + this.creationMethod = creationMethod; + return this; + } + + public ComponentCreationParameters build() { + return new ComponentCreationParameters(newComponent, userUuid, userLogin, mainBranchName, isManaged, creationMethod); + } + } +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/ComponentUpdater.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/ComponentUpdater.java new file mode 100644 index 00000000000..e42e77a33e1 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/ComponentUpdater.java @@ -0,0 +1,259 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.component; + +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.Scopes; +import org.sonar.api.utils.System2; +import org.sonar.core.i18n.I18n; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.BranchDto; +import org.sonar.db.component.BranchType; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.portfolio.PortfolioDto; +import org.sonar.db.portfolio.PortfolioDto.SelectionMode; +import org.sonar.db.project.CreationMethod; +import org.sonar.db.project.ProjectDto; +import org.sonar.db.user.UserDto; +import org.sonar.server.common.permission.Operation; +import org.sonar.server.common.permission.PermissionTemplateService; +import org.sonar.server.common.permission.PermissionUpdater; +import org.sonar.server.common.permission.UserPermissionChange; +import org.sonar.server.component.ComponentCreationData; +import org.sonar.server.es.Indexers; +import org.sonar.server.favorite.FavoriteUpdater; +import org.sonar.server.permission.PermissionService; +import org.sonar.server.project.DefaultBranchNameResolver; + +import static com.google.common.base.Preconditions.checkState; +import static java.util.Collections.singletonList; +import static org.sonar.api.web.UserRole.PUBLIC_PERMISSIONS; +import static org.sonar.core.component.ComponentKeys.ALLOWED_CHARACTERS_MESSAGE; +import static org.sonar.core.component.ComponentKeys.isValidProjectKey; +import static org.sonar.server.exceptions.BadRequestException.checkRequest; +import static org.sonar.server.exceptions.BadRequestException.throwBadRequestException; + +public class ComponentUpdater { + + private static final Set PROJ_APP_QUALIFIERS = Set.of(Qualifiers.PROJECT, Qualifiers.APP); + private static final String KEY_ALREADY_EXISTS_ERROR = "Could not create %s with key: \"%s\". A similar key already exists: \"%s\""; + private static final String MALFORMED_KEY_ERROR = "Malformed key for %s: '%s'. %s."; + private final DbClient dbClient; + private final I18n i18n; + private final System2 system2; + private final PermissionTemplateService permissionTemplateService; + private final FavoriteUpdater favoriteUpdater; + private final Indexers indexers; + private final UuidFactory uuidFactory; + private final DefaultBranchNameResolver defaultBranchNameResolver; + private final PermissionUpdater userPermissionUpdater; + private final PermissionService permissionService; + + public ComponentUpdater(DbClient dbClient, I18n i18n, System2 system2, + PermissionTemplateService permissionTemplateService, FavoriteUpdater favoriteUpdater, + Indexers indexers, UuidFactory uuidFactory, DefaultBranchNameResolver defaultBranchNameResolver, PermissionUpdater userPermissionUpdater, + PermissionService permissionService) { + this.dbClient = dbClient; + this.i18n = i18n; + this.system2 = system2; + this.permissionTemplateService = permissionTemplateService; + this.favoriteUpdater = favoriteUpdater; + this.indexers = indexers; + this.uuidFactory = uuidFactory; + this.defaultBranchNameResolver = defaultBranchNameResolver; + this.userPermissionUpdater = userPermissionUpdater; + this.permissionService = permissionService; + } + + /** + * - Create component + * - Apply default permission template + * - Add component to favorite if the component has the 'Project Creators' permission + * - Index component in es indexes + */ + public ComponentCreationData create(DbSession dbSession, ComponentCreationParameters componentCreationParameters) { + ComponentCreationData componentCreationData = createWithoutCommit(dbSession, componentCreationParameters); + commitAndIndex(dbSession, componentCreationData); + return componentCreationData; + } + + public void commitAndIndex(DbSession dbSession, ComponentCreationData componentCreationData) { + if (componentCreationData.portfolioDto() != null) { + indexers.commitAndIndexEntities(dbSession, singletonList(componentCreationData.portfolioDto()), Indexers.EntityEvent.CREATION); + } else if (componentCreationData.projectDto() != null) { + indexers.commitAndIndexEntities(dbSession, singletonList(componentCreationData.projectDto()), Indexers.EntityEvent.CREATION); + } + } + + /** + * Create component without committing. + * Don't forget to call commitAndIndex(...) when ready to commit. + */ + public ComponentCreationData createWithoutCommit(DbSession dbSession, ComponentCreationParameters componentCreationParameters) { + checkKeyFormat(componentCreationParameters.newComponent().qualifier(), componentCreationParameters.newComponent().key()); + checkKeyAlreadyExists(dbSession, componentCreationParameters.newComponent()); + + long now = system2.now(); + + ComponentDto componentDto = createRootComponent(dbSession, componentCreationParameters.newComponent(), now); + + BranchDto mainBranch = null; + ProjectDto projectDto = null; + PortfolioDto portfolioDto = null; + + if (isProjectOrApp(componentDto)) { + projectDto = toProjectDto(componentDto, now, componentCreationParameters.creationMethod()); + dbClient.projectDao().insert(dbSession, projectDto); + addToFavourites(dbSession, projectDto, componentCreationParameters.userUuid(), componentCreationParameters.userLogin()); + mainBranch = createMainBranch(dbSession, componentDto.uuid(), projectDto.getUuid(), componentCreationParameters.mainBranchName()); + if (componentCreationParameters.isManaged()) { + applyPublicPermissionsForCreator(dbSession, projectDto, componentCreationParameters.userUuid()); + } else { + permissionTemplateService.applyDefaultToNewComponent(dbSession, projectDto, componentCreationParameters.userUuid()); + } + } else if (isPortfolio(componentDto)) { + portfolioDto = toPortfolioDto(componentDto, now); + dbClient.portfolioDao().insert(dbSession, portfolioDto, false); + permissionTemplateService.applyDefaultToNewComponent(dbSession, portfolioDto, componentCreationParameters.userUuid()); + } else { + throw new IllegalArgumentException("Component " + componentDto + " is not a top level entity"); + } + + return new ComponentCreationData(componentDto, portfolioDto, mainBranch, projectDto); + } + + private void applyPublicPermissionsForCreator(DbSession dbSession, ProjectDto projectDto, @Nullable String userUuid) { + if (userUuid != null) { + UserDto userDto = dbClient.userDao().selectByUuid(dbSession, userUuid); + checkState(userDto != null, "User with uuid '%s' doesn't exist", userUuid); + userPermissionUpdater.apply(dbSession, + PUBLIC_PERMISSIONS.stream() + .map(permission -> toUserPermissionChange(permission, projectDto, userDto)) + .collect(Collectors.toSet())); + } + } + + private UserPermissionChange toUserPermissionChange(String permission, ProjectDto projectDto, UserDto userDto) { + return new UserPermissionChange(Operation.ADD, permission, projectDto, userDto, permissionService); + } + + private void addToFavourites(DbSession dbSession, ProjectDto projectDto, @Nullable String userUuid, @Nullable String userLogin) { + if (permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(dbSession, projectDto)) { + favoriteUpdater.add(dbSession, projectDto, userUuid, userLogin, false); + } + } + + private void checkKeyFormat(String qualifier, String key) { + checkRequest(isValidProjectKey(key), MALFORMED_KEY_ERROR, getQualifierToDisplay(qualifier), key, ALLOWED_CHARACTERS_MESSAGE); + } + + private void checkKeyAlreadyExists(DbSession dbSession, NewComponent newComponent) { + List componentDtos = dbClient.componentDao().selectByKeyCaseInsensitive(dbSession, newComponent.key()); + + if (!componentDtos.isEmpty()) { + String alreadyExistingKeys = componentDtos + .stream() + .map(ComponentDto::getKey) + .collect(Collectors.joining(", ")); + throwBadRequestException(KEY_ALREADY_EXISTS_ERROR, getQualifierToDisplay(newComponent.qualifier()), newComponent.key(), alreadyExistingKeys); + } + } + + private ComponentDto createRootComponent(DbSession session, NewComponent newComponent, long now) { + String uuid = uuidFactory.create(); + + ComponentDto component = new ComponentDto() + .setUuid(uuid) + .setUuidPath(ComponentDto.UUID_PATH_OF_ROOT) + .setBranchUuid(uuid) + .setKey(newComponent.key()) + .setName(newComponent.name()) + .setDescription(newComponent.description()) + .setLongName(newComponent.name()) + .setScope(Scopes.PROJECT) + .setQualifier(newComponent.qualifier()) + .setPrivate(newComponent.isPrivate()) + .setCreatedAt(new Date(now)); + + dbClient.componentDao().insert(session, component, true); + return component; + } + + private ProjectDto toProjectDto(ComponentDto component, long now, CreationMethod creationMethod) { + return new ProjectDto() + .setUuid(uuidFactory.create()) + .setKey(component.getKey()) + .setQualifier(component.qualifier()) + .setName(component.name()) + .setPrivate(component.isPrivate()) + .setDescription(component.description()) + .setCreationMethod(creationMethod) + .setUpdatedAt(now) + .setCreatedAt(now); + } + + private static PortfolioDto toPortfolioDto(ComponentDto component, long now) { + return new PortfolioDto() + .setUuid(component.uuid()) + .setRootUuid(component.branchUuid()) + .setKey(component.getKey()) + .setName(component.name()) + .setPrivate(component.isPrivate()) + .setDescription(component.description()) + .setSelectionMode(SelectionMode.NONE.name()) + .setUpdatedAt(now) + .setCreatedAt(now); + } + + private static boolean isProjectOrApp(ComponentDto componentDto) { + return PROJ_APP_QUALIFIERS.contains(componentDto.qualifier()); + } + + private static boolean isPortfolio(ComponentDto componentDto) { + return Qualifiers.VIEW.contains(componentDto.qualifier()); + } + + private BranchDto createMainBranch(DbSession session, String componentUuid, String projectUuid, @Nullable String mainBranch) { + BranchDto branch = new BranchDto() + .setBranchType(BranchType.BRANCH) + .setUuid(componentUuid) + .setIsMain(true) + .setKey(Optional.ofNullable(mainBranch).orElse(defaultBranchNameResolver.getEffectiveMainBranchName())) + .setMergeBranchUuid(null) + .setExcludeFromPurge(true) + .setProjectUuid(projectUuid); + dbClient.branchDao().upsert(session, branch); + return branch; + } + + private String getQualifierToDisplay(String qualifier) { + return i18n.message(Locale.getDefault(), "qualifier." + qualifier, "Project"); + } + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/NewComponent.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/NewComponent.java new file mode 100644 index 00000000000..8167bdae6ab --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/NewComponent.java @@ -0,0 +1,120 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.component; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import static org.sonar.api.resources.Qualifiers.PROJECT; +import static org.sonar.db.component.ComponentValidator.checkComponentKey; +import static org.sonar.db.component.ComponentValidator.checkComponentName; +import static org.sonar.db.component.ComponentValidator.checkComponentQualifier; + +@Immutable +public class NewComponent { + private final String key; + private final String qualifier; + private final String name; + private final String description; + private final boolean isPrivate; + + private NewComponent(NewComponent.Builder builder) { + this.key = builder.key; + this.qualifier = builder.qualifier; + this.name = builder.name; + this.isPrivate = builder.isPrivate; + this.description = builder.description; + } + + public static Builder newComponentBuilder() { + return new Builder(); + } + + public String key() { + return key; + } + + public String name() { + return name; + } + + public String qualifier() { + return qualifier; + } + + public boolean isPrivate() { + return isPrivate; + } + + @CheckForNull + public String description() { + return description; + } + + public boolean isProject() { + return PROJECT.equals(qualifier); + } + + public static class Builder { + private String description; + private String key; + private String qualifier = PROJECT; + private String name; + private boolean isPrivate = false; + + private Builder() { + // use static factory method newComponentBuilder() + } + + public Builder setKey(String key) { + this.key = key; + return this; + } + + public Builder setQualifier(String qualifier) { + this.qualifier = qualifier; + return this; + } + + public Builder setName(String name) { + this.name = name; + return this; + } + + public Builder setPrivate(boolean isPrivate) { + this.isPrivate = isPrivate; + return this; + } + + public Builder setDescription(@Nullable String description) { + this.description = description; + return this; + } + + public NewComponent build() { + checkComponentKey(key); + checkComponentName(name); + checkComponentQualifier(qualifier); + return new NewComponent(this); + } + } + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/package-info.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/package-info.java new file mode 100644 index 00000000000..51d90d85a8c --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.common.component; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/newcodeperiod/CaycUtils.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/newcodeperiod/CaycUtils.java new file mode 100644 index 00000000000..b206539df16 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/newcodeperiod/CaycUtils.java @@ -0,0 +1,39 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.newcodeperiod; + +import org.sonar.db.newcodeperiod.NewCodePeriodType; + +public interface CaycUtils { + static boolean isNewCodePeriodCompliant(NewCodePeriodType type, String value) { + if (type == NewCodePeriodType.NUMBER_OF_DAYS) { + return parseDays(value) > 0 && parseDays(value) <= 90; + } + return true; + } + + static int parseDays(String value) { + try { + return Integer.parseInt(value); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to parse number of days: " + value); + } + } +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/newcodeperiod/NewCodeDefinitionResolver.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/newcodeperiod/NewCodeDefinitionResolver.java new file mode 100644 index 00000000000..8956831e8cf --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/newcodeperiod/NewCodeDefinitionResolver.java @@ -0,0 +1,149 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.newcodeperiod; + +import com.google.common.base.Preconditions; +import java.util.EnumSet; +import java.util.Locale; +import java.util.Optional; +import javax.annotation.Nullable; +import org.sonar.core.platform.EditionProvider; +import org.sonar.core.platform.PlatformEditionProvider; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.newcodeperiod.NewCodePeriodDto; +import org.sonar.db.newcodeperiod.NewCodePeriodParser; +import org.sonar.db.newcodeperiod.NewCodePeriodType; + +import static org.sonar.db.newcodeperiod.NewCodePeriodType.NUMBER_OF_DAYS; +import static org.sonar.db.newcodeperiod.NewCodePeriodType.PREVIOUS_VERSION; +import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH; + +public class NewCodeDefinitionResolver { + private static final String BEGIN_LIST = "
    "; + + private static final String END_LIST = "
"; + private static final String BEGIN_ITEM_LIST = "
  • "; + private static final String END_ITEM_LIST = "
  • "; + + public static final String NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION = "Project New Code Definition Type
    " + + "New code definitions of the following types are allowed:" + + BEGIN_LIST + + BEGIN_ITEM_LIST + PREVIOUS_VERSION.name() + END_ITEM_LIST + + BEGIN_ITEM_LIST + NUMBER_OF_DAYS.name() + END_ITEM_LIST + + BEGIN_ITEM_LIST + REFERENCE_BRANCH.name() + " - will default to the main branch." + END_ITEM_LIST + + END_LIST; + + public static final String NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION = "Project New Code Definition Value
    " + + "For each new code definition type, a different value is expected:" + + BEGIN_LIST + + BEGIN_ITEM_LIST + "no value, when the new code definition type is " + PREVIOUS_VERSION.name() + " and " + REFERENCE_BRANCH.name() + END_ITEM_LIST + + BEGIN_ITEM_LIST + "a number between 1 and 90, when the new code definition type is " + NUMBER_OF_DAYS.name() + END_ITEM_LIST + + END_LIST; + + private static final String UNEXPECTED_VALUE_ERROR_MESSAGE = "Unexpected value for newCodeDefinitionType '%s'"; + + private static final EnumSet projectCreationNCDTypes = EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS, REFERENCE_BRANCH); + + private final DbClient dbClient; + private final PlatformEditionProvider editionProvider; + + public NewCodeDefinitionResolver(DbClient dbClient, PlatformEditionProvider editionProvider) { + this.dbClient = dbClient; + this.editionProvider = editionProvider; + } + + public void createNewCodeDefinition(DbSession dbSession, String projectUuid, String mainBranchUuid, + String defaultBranchName, String newCodeDefinitionType, @Nullable String newCodeDefinitionValue) { + + boolean isCommunityEdition = editionProvider.get().filter(EditionProvider.Edition.COMMUNITY::equals).isPresent(); + NewCodePeriodType newCodePeriodType = parseNewCodeDefinitionType(newCodeDefinitionType); + + NewCodePeriodDto dto = new NewCodePeriodDto(); + dto.setType(newCodePeriodType); + dto.setProjectUuid(projectUuid); + + if (isCommunityEdition) { + dto.setBranchUuid(mainBranchUuid); + } + + getNewCodeDefinitionValueProjectCreation(newCodePeriodType, newCodeDefinitionValue, defaultBranchName).ifPresent(dto::setValue); + + if (!CaycUtils.isNewCodePeriodCompliant(dto.getType(), dto.getValue())) { + throw new IllegalArgumentException("Failed to set the New Code Definition. The given value is not compatible with the Clean as You Code methodology. " + + "Please refer to the documentation for compliant options."); + } + + dbClient.newCodePeriodDao().insert(dbSession, dto); + } + + public static void checkNewCodeDefinitionParam(@Nullable String newCodeDefinitionType, @Nullable String newCodeDefinitionValue) { + if (newCodeDefinitionType == null && newCodeDefinitionValue != null) { + throw new IllegalArgumentException("New code definition type is required when new code definition value is provided"); + } + } + + private static Optional getNewCodeDefinitionValueProjectCreation(NewCodePeriodType type, @Nullable String value, String defaultBranchName) { + return switch (type) { + case PREVIOUS_VERSION -> { + Preconditions.checkArgument(value == null, UNEXPECTED_VALUE_ERROR_MESSAGE, type); + yield Optional.empty(); + } + case NUMBER_OF_DAYS -> { + requireValue(type, value); + yield Optional.of(parseDays(value)); + } + case REFERENCE_BRANCH -> { + Preconditions.checkArgument(value == null, UNEXPECTED_VALUE_ERROR_MESSAGE, type); + yield Optional.of(defaultBranchName); + } + default -> throw new IllegalStateException("Unexpected type: " + type); + }; + } + + private static String parseDays(String value) { + try { + return Integer.toString(NewCodePeriodParser.parseDays(value)); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to parse number of days: " + value); + } + } + + private static void requireValue(NewCodePeriodType type, @Nullable String value) { + Preconditions.checkArgument(value != null, "New code definition type '%s' requires a newCodeDefinitionValue", type); + } + + private static NewCodePeriodType parseNewCodeDefinitionType(String typeStr) { + NewCodePeriodType type; + try { + type = NewCodePeriodType.valueOf(typeStr.toUpperCase(Locale.US)); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid type: " + typeStr); + } + validateType(type); + return type; + } + + private static void validateType(NewCodePeriodType type) { + Preconditions.checkArgument(projectCreationNCDTypes.contains(type), "Invalid type '%s'. `newCodeDefinitionType` can only be set with types: %s", + type, projectCreationNCDTypes); + } + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/newcodeperiod/package-info.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/newcodeperiod/package-info.java new file mode 100644 index 00000000000..47a06a53a29 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/newcodeperiod/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.common.newcodeperiod; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/DefaultTemplatesResolver.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/DefaultTemplatesResolver.java new file mode 100644 index 00000000000..471d49723bf --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/DefaultTemplatesResolver.java @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.permission; + +import java.util.Optional; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import org.sonar.db.DbSession; + +import static java.util.Objects.requireNonNull; +import static java.util.Optional.ofNullable; + +public interface DefaultTemplatesResolver { + /** + * Resolve the effective default templates uuid for the specified {@link DefaultTemplates}. + *
      + *
    • {@link ResolvedDefaultTemplates#project} is always the same as {@link DefaultTemplates#projectUuid}
    • + *
    • when Governance is not installed, {@link ResolvedDefaultTemplates#application} is always {@code null}
    • + *
    • when Governance is installed, {@link ResolvedDefaultTemplates#application} is {@link DefaultTemplates#applicationsUuid} + * when it is non {@code null}, otherwise it is {@link DefaultTemplates#projectUuid}
    • + *
    • when Governance is installed, {@link ResolvedDefaultTemplates#portfolio} is {@link DefaultTemplates#portfoliosUuid} + * when it is non {@code null}, otherwise it is {@link DefaultTemplates#projectUuid}
    • + *
    + */ + ResolvedDefaultTemplates resolve(DbSession dbSession); + + @Immutable + final class ResolvedDefaultTemplates { + private final String project; + private final String application; + private final String portfolio; + + public ResolvedDefaultTemplates(String project, @Nullable String application, @Nullable String portfolio) { + this.project = requireNonNull(project, "project can't be null"); + this.application = application; + this.portfolio = portfolio; + } + + public String getProject() { + return project; + } + + public Optional getApplication() { + return ofNullable(application); + } + + public Optional getPortfolio() { + return ofNullable(portfolio); + } + } +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/DefaultTemplatesResolverImpl.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/DefaultTemplatesResolverImpl.java new file mode 100644 index 00000000000..ec32b6d3cf6 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/DefaultTemplatesResolverImpl.java @@ -0,0 +1,71 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.permission; + +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.ResourceType; +import org.sonar.api.resources.ResourceTypes; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.server.property.InternalProperties; + +public class DefaultTemplatesResolverImpl implements DefaultTemplatesResolver { + + private final DbClient dbClient; + private final ResourceTypes resourceTypes; + + public DefaultTemplatesResolverImpl(DbClient dbClient, ResourceTypes resourceTypes) { + this.dbClient = dbClient; + this.resourceTypes = resourceTypes; + } + + @Override + public ResolvedDefaultTemplates resolve(DbSession dbSession) { + String defaultProjectTemplate = dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.DEFAULT_PROJECT_TEMPLATE).orElseThrow(() -> { + throw new IllegalStateException("Default template for project is missing"); + }); + + String defaultPortfolioTemplate = null; + String defaultApplicationTemplate = null; + + if (isPortfolioEnabled(resourceTypes)) { + defaultPortfolioTemplate = dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.DEFAULT_PORTFOLIO_TEMPLATE).orElse(defaultProjectTemplate); + } + if (isApplicationEnabled(resourceTypes)) { + defaultApplicationTemplate = dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.DEFAULT_APPLICATION_TEMPLATE).orElse(defaultProjectTemplate); + } + return new ResolvedDefaultTemplates(defaultProjectTemplate, defaultApplicationTemplate, defaultPortfolioTemplate); + } + + private static boolean isPortfolioEnabled(ResourceTypes resourceTypes) { + return resourceTypes.getRoots() + .stream() + .map(ResourceType::getQualifier) + .anyMatch(Qualifiers.VIEW::equals); + } + + private static boolean isApplicationEnabled(ResourceTypes resourceTypes) { + return resourceTypes.getRoots() + .stream() + .map(ResourceType::getQualifier) + .anyMatch(Qualifiers.APP::equals); + } + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/GranteeTypeSpecificPermissionUpdater.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/GranteeTypeSpecificPermissionUpdater.java new file mode 100644 index 00000000000..76a7602a91d --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/GranteeTypeSpecificPermissionUpdater.java @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.permission; + +import java.util.Set; +import javax.annotation.Nullable; +import org.sonar.db.DbSession; + +public interface GranteeTypeSpecificPermissionUpdater { + Class getHandledClass(); + + Set loadExistingEntityPermissions(DbSession dbSession, String uuidOfGrantee, @Nullable String entityUuid); + + boolean apply(DbSession dbSession, Set existingPermissions, T change); +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/GroupPermissionChange.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/GroupPermissionChange.java new file mode 100644 index 00000000000..cb655361bdf --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/GroupPermissionChange.java @@ -0,0 +1,53 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.permission; + +import java.util.Optional; +import javax.annotation.Nullable; +import org.sonar.db.entity.EntityDto; +import org.sonar.db.user.GroupDto; +import org.sonar.server.permission.GroupUuidOrAnyone; +import org.sonar.server.permission.PermissionService; + +public class GroupPermissionChange extends PermissionChange { + + private final GroupDto groupDto; + + public GroupPermissionChange(Operation operation, String permission, @Nullable EntityDto entityDto, + @Nullable GroupDto groupDto, PermissionService permissionService) { + super(operation, permission, entityDto, permissionService); + this.groupDto = groupDto; + } + + public GroupUuidOrAnyone getGroupUuidOrAnyone() { + return GroupUuidOrAnyone.from(groupDto); + } + + public Optional getGroupName() { + return Optional.ofNullable(groupDto).map(GroupDto::getName); + } + + @Override + public String getUuidOfGrantee() { + return getGroupUuidOrAnyone().getUuid(); + } + + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/GroupPermissionChanger.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/GroupPermissionChanger.java new file mode 100644 index 00000000000..23c893ef1ae --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/GroupPermissionChanger.java @@ -0,0 +1,182 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.permission; + +import java.util.HashSet; +import java.util.Set; +import javax.annotation.Nullable; +import org.sonar.api.web.UserRole; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.entity.EntityDto; +import org.sonar.db.permission.GlobalPermission; +import org.sonar.db.permission.GroupPermissionDto; +import org.sonar.server.exceptions.BadRequestException; +import org.sonar.server.permission.GroupUuidOrAnyone; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.String.format; +import static org.sonar.server.common.permission.Operation.ADD; +import static org.sonar.server.common.permission.Operation.REMOVE; +import static org.sonar.server.exceptions.BadRequestException.checkRequest; + +public class GroupPermissionChanger implements GranteeTypeSpecificPermissionUpdater { + + private final DbClient dbClient; + private final UuidFactory uuidFactory; + + public GroupPermissionChanger(DbClient dbClient, UuidFactory uuidFactory) { + this.dbClient = dbClient; + this.uuidFactory = uuidFactory; + } + + @Override + public Class getHandledClass() { + return GroupPermissionChange.class; + } + + @Override + public Set loadExistingEntityPermissions(DbSession dbSession, String uuidOfGrantee, @Nullable String entityUuid) { + if (entityUuid != null) { + return new HashSet<>(dbClient.groupPermissionDao().selectEntityPermissionsOfGroup(dbSession, uuidOfGrantee, entityUuid)); + } + return new HashSet<>(dbClient.groupPermissionDao().selectGlobalPermissionsOfGroup(dbSession, uuidOfGrantee)); + } + + @Override + public boolean apply(DbSession dbSession, Set existingPermissions, GroupPermissionChange change) { + ensureConsistencyWithVisibility(change); + if (isImplicitlyAlreadyDone(change)) { + return false; + } + switch (change.getOperation()) { + case ADD: + if (existingPermissions.contains(change.getPermission())) { + return false; + } + return addPermission(dbSession, change); + case REMOVE: + if (!existingPermissions.contains(change.getPermission())) { + return false; + } + return removePermission(dbSession, change); + default: + throw new UnsupportedOperationException("Unsupported permission change: " + change.getOperation()); + } + } + + private static boolean isImplicitlyAlreadyDone(GroupPermissionChange change) { + EntityDto project = change.getEntity(); + if (project != null) { + return isImplicitlyAlreadyDone(project, change); + } + return false; + } + + private static boolean isImplicitlyAlreadyDone(EntityDto project, GroupPermissionChange change) { + return isAttemptToAddPublicPermissionToPublicComponent(change, project) + || isAttemptToRemovePermissionFromAnyoneOnPrivateComponent(change, project); + } + + private static boolean isAttemptToAddPublicPermissionToPublicComponent(GroupPermissionChange change, EntityDto project) { + return !project.isPrivate() + && change.getOperation() == ADD + && UserRole.PUBLIC_PERMISSIONS.contains(change.getPermission()); + } + + private static boolean isAttemptToRemovePermissionFromAnyoneOnPrivateComponent(GroupPermissionChange change, EntityDto project) { + return project.isPrivate() + && change.getOperation() == REMOVE + && change.getGroupUuidOrAnyone().isAnyone(); + } + + private static void ensureConsistencyWithVisibility(GroupPermissionChange change) { + EntityDto project = change.getEntity(); + if (project != null) { + checkRequest( + !isAttemptToAddPermissionToAnyoneOnPrivateComponent(change, project), + "No permission can be granted to Anyone on a private component"); + BadRequestException.checkRequest( + !isAttemptToRemovePublicPermissionFromPublicComponent(change, project), + "Permission %s can't be removed from a public component", change.getPermission()); + } + } + + private static boolean isAttemptToAddPermissionToAnyoneOnPrivateComponent(GroupPermissionChange change, EntityDto project) { + return project.isPrivate() + && change.getOperation() == ADD + && change.getGroupUuidOrAnyone().isAnyone(); + } + + private static boolean isAttemptToRemovePublicPermissionFromPublicComponent(GroupPermissionChange change, EntityDto project) { + return !project.isPrivate() + && change.getOperation() == REMOVE + && UserRole.PUBLIC_PERMISSIONS.contains(change.getPermission()); + } + + private boolean addPermission(DbSession dbSession, GroupPermissionChange change) { + validateNotAnyoneAndAdminPermission(change.getPermission(), change.getGroupUuidOrAnyone()); + + String groupUuid = change.getGroupUuidOrAnyone().getUuid(); + String groupName = change.getGroupName().orElse(null); + + GroupPermissionDto addedDto = new GroupPermissionDto() + .setUuid(uuidFactory.create()) + .setRole(change.getPermission()) + .setGroupUuid(groupUuid) + .setEntityName(change.getProjectName()) + .setEntityUuid(change.getProjectUuid()) + .setGroupName(groupName); + + dbClient.groupPermissionDao().insert(dbSession, addedDto, change.getEntity(), null); + return true; + } + + private static void validateNotAnyoneAndAdminPermission(String permission, GroupUuidOrAnyone group) { + checkRequest(!GlobalPermission.ADMINISTER.getKey().equals(permission) || !group.isAnyone(), + format("It is not possible to add the '%s' permission to group 'Anyone'.", permission)); + } + + private boolean removePermission(DbSession dbSession, GroupPermissionChange change) { + checkIfRemainingGlobalAdministrators(dbSession, change); + String groupUuid = change.getGroupUuidOrAnyone().getUuid(); + String groupName = change.getGroupName().orElse(null); + dbClient.groupPermissionDao().delete(dbSession, + change.getPermission(), + groupUuid, + groupName, + change.getEntity()); + return true; + } + + private void checkIfRemainingGlobalAdministrators(DbSession dbSession, GroupPermissionChange change) { + GroupUuidOrAnyone groupUuidOrAnyone = change.getGroupUuidOrAnyone(); + if (GlobalPermission.ADMINISTER.getKey().equals(change.getPermission()) && + !groupUuidOrAnyone.isAnyone() && + change.getProjectUuid() == null) { + String groupUuid = checkNotNull(groupUuidOrAnyone.getUuid()); + // removing global admin permission from group + int remaining = dbClient.authorizationDao().countUsersWithGlobalPermissionExcludingGroup(dbSession, GlobalPermission.ADMINISTER.getKey(), groupUuid); + checkRequest(remaining > 0, "Last group with permission '%s'. Permission cannot be removed.", GlobalPermission.ADMINISTER.getKey()); + } + } + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/PermissionChange.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/PermissionChange.java new file mode 100644 index 00000000000..30173c3def7 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/PermissionChange.java @@ -0,0 +1,77 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.permission; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.sonar.db.entity.EntityDto; +import org.sonar.db.permission.GlobalPermission; +import org.sonar.server.permission.PermissionService; + +import static java.util.Objects.requireNonNull; +import static org.sonar.server.exceptions.BadRequestException.checkRequest; + +public abstract class PermissionChange { + + private final Operation operation; + private final String permission; + private final EntityDto entity; + protected final PermissionService permissionService; + + protected PermissionChange(Operation operation, String permission, @Nullable EntityDto entity, PermissionService permissionService) { + this.operation = requireNonNull(operation); + this.permission = requireNonNull(permission); + this.entity = entity; + this.permissionService = permissionService; + if (entity == null) { + checkRequest(permissionService.getGlobalPermissions().stream().anyMatch(p -> p.getKey().equals(permission)), + "Invalid global permission '%s'. Valid values are %s", permission, + permissionService.getGlobalPermissions().stream().map(GlobalPermission::getKey).toList()); + } else { + checkRequest(permissionService.getAllProjectPermissions().contains(permission), "Invalid project permission '%s'. Valid values are %s", permission, + permissionService.getAllProjectPermissions()); + } + } + + public Operation getOperation() { + return operation; + } + + public String getPermission() { + return permission; + } + + @CheckForNull + public EntityDto getEntity() { + return entity; + } + + @CheckForNull + public String getProjectName() { + return entity == null ? null : entity.getName(); + } + + @CheckForNull + public String getProjectUuid() { + return entity == null ? null : entity.getUuid(); + } + + public abstract String getUuidOfGrantee(); +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/PermissionTemplateService.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/PermissionTemplateService.java new file mode 100644 index 00000000000..3857d0c3f9c --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/PermissionTemplateService.java @@ -0,0 +1,243 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.permission; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.StringUtils; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.server.ServerSide; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.entity.EntityDto; +import org.sonar.db.permission.GroupPermissionDto; +import org.sonar.db.permission.UserPermissionDto; +import org.sonar.db.permission.template.PermissionTemplateCharacteristicDto; +import org.sonar.db.permission.template.PermissionTemplateDto; +import org.sonar.db.permission.template.PermissionTemplateGroupDto; +import org.sonar.db.permission.template.PermissionTemplateUserDto; +import org.sonar.db.project.ProjectDto; +import org.sonar.db.user.UserDto; +import org.sonar.db.user.UserId; +import org.sonar.server.es.Indexers; +import org.sonar.server.exceptions.TemplateMatchingKeyException; +import org.sonar.server.user.UserSession; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.String.format; +import static java.util.Collections.singletonList; +import static org.sonar.api.security.DefaultGroups.isAnyone; +import static org.sonar.api.web.UserRole.PUBLIC_PERMISSIONS; +import static org.sonar.db.permission.GlobalPermission.SCAN; + +@ServerSide +public class PermissionTemplateService { + + private final DbClient dbClient; + private final Indexers indexers; + private final UserSession userSession; + private final DefaultTemplatesResolver defaultTemplatesResolver; + private final UuidFactory uuidFactory; + + public PermissionTemplateService(DbClient dbClient, Indexers indexers, UserSession userSession, + DefaultTemplatesResolver defaultTemplatesResolver, UuidFactory uuidFactory) { + this.dbClient = dbClient; + this.indexers = indexers; + this.userSession = userSession; + this.defaultTemplatesResolver = defaultTemplatesResolver; + this.uuidFactory = uuidFactory; + } + + public boolean wouldUserHaveScanPermissionWithDefaultTemplate(DbSession dbSession, @Nullable String userUuid, String projectKey) { + if (userSession.hasPermission(SCAN)) { + return true; + } + + ProjectDto projectDto = new ProjectDto().setKey(projectKey).setQualifier(Qualifiers.PROJECT); + PermissionTemplateDto template = findTemplate(dbSession, projectDto); + if (template == null) { + return false; + } + + List potentialPermissions = dbClient.permissionTemplateDao().selectPotentialPermissionsByUserUuidAndTemplateUuid(dbSession, userUuid, template.getUuid()); + return potentialPermissions.contains(SCAN.getKey()); + } + + /** + * Apply a permission template to a set of projects. Authorization to administrate these projects + * is not verified. The projects must exist, so the "project creator" permissions defined in the + * template are ignored. + */ + public void applyAndCommit(DbSession dbSession, PermissionTemplateDto template, Collection entities) { + if (entities.isEmpty()) { + return; + } + + for (EntityDto entity : entities) { + dbClient.groupPermissionDao().deleteByEntityUuid(dbSession, entity); + dbClient.userPermissionDao().deleteEntityPermissions(dbSession, entity); + copyPermissions(dbSession, template, entity, null); + } + indexers.commitAndIndexEntities(dbSession, entities, Indexers.EntityEvent.PERMISSION_CHANGE); + } + + /** + * Apply the default permission template to a new project (has no permissions yet). + * + * @param projectCreatorUserId id of the user creating the project. + */ + public void applyDefaultToNewComponent(DbSession dbSession, EntityDto entityDto, @Nullable String projectCreatorUserId) { + PermissionTemplateDto template = findTemplate(dbSession, entityDto); + checkArgument(template != null, "Cannot retrieve default permission template"); + copyPermissions(dbSession, template, entityDto, projectCreatorUserId); + } + + public boolean hasDefaultTemplateWithPermissionOnProjectCreator(DbSession dbSession, ProjectDto projectDto) { + PermissionTemplateDto template = findTemplate(dbSession, projectDto); + return hasProjectCreatorPermission(dbSession, template); + } + + private boolean hasProjectCreatorPermission(DbSession dbSession, @Nullable PermissionTemplateDto template) { + return template != null && dbClient.permissionTemplateCharacteristicDao().selectByTemplateUuids(dbSession, singletonList(template.getUuid())).stream() + .anyMatch(PermissionTemplateCharacteristicDto::getWithProjectCreator); + } + + private void copyPermissions(DbSession dbSession, PermissionTemplateDto template, EntityDto entity, @Nullable String projectCreatorUserUuid) { + List usersPermissions = dbClient.permissionTemplateDao().selectUserPermissionsByTemplateId(dbSession, template.getUuid()); + Set permissionTemplateUserUuids = usersPermissions.stream().map(PermissionTemplateUserDto::getUserUuid).collect(Collectors.toSet()); + Map userIdByUuid = dbClient.userDao().selectByUuids(dbSession, permissionTemplateUserUuids).stream().collect(Collectors.toMap(UserDto::getUuid, u -> u)); + usersPermissions + .stream() + .filter(up -> permissionValidForProject(entity.isPrivate(), up.getPermission())) + .forEach(up -> { + UserPermissionDto dto = new UserPermissionDto(uuidFactory.create(), up.getPermission(), up.getUserUuid(), entity.getUuid()); + dbClient.userPermissionDao().insert(dbSession, dto, entity, userIdByUuid.get(up.getUserUuid()), template); + }); + + List groupsPermissions = dbClient.permissionTemplateDao().selectGroupPermissionsByTemplateUuid(dbSession, template.getUuid()); + groupsPermissions + .stream() + .filter(gp -> groupNameValidForProject(entity.isPrivate(), gp.getGroupName())) + .filter(gp -> permissionValidForProject(entity.isPrivate(), gp.getPermission())) + .forEach(gp -> { + String groupUuid = isAnyone(gp.getGroupName()) ? null : gp.getGroupUuid(); + String groupName = groupUuid == null ? null : dbClient.groupDao().selectByUuid(dbSession, groupUuid).getName(); + GroupPermissionDto dto = new GroupPermissionDto() + .setUuid(uuidFactory.create()) + .setGroupUuid(groupUuid) + .setGroupName(groupName) + .setRole(gp.getPermission()) + .setEntityUuid(entity.getUuid()) + .setEntityName(entity.getName()); + + dbClient.groupPermissionDao().insert(dbSession, dto, entity, template); + }); + + List characteristics = dbClient.permissionTemplateCharacteristicDao().selectByTemplateUuids(dbSession, singletonList(template.getUuid())); + if (projectCreatorUserUuid != null) { + Set permissionsForCurrentUserAlreadyInDb = usersPermissions.stream() + .filter(userPermission -> projectCreatorUserUuid.equals(userPermission.getUserUuid())) + .map(PermissionTemplateUserDto::getPermission) + .collect(java.util.stream.Collectors.toSet()); + + UserDto userDto = dbClient.userDao().selectByUuid(dbSession, projectCreatorUserUuid); + characteristics.stream() + .filter(PermissionTemplateCharacteristicDto::getWithProjectCreator) + .filter(up -> permissionValidForProject(entity.isPrivate(), up.getPermission())) + .filter(characteristic -> !permissionsForCurrentUserAlreadyInDb.contains(characteristic.getPermission())) + .forEach(c -> { + UserPermissionDto dto = new UserPermissionDto(uuidFactory.create(), c.getPermission(), userDto.getUuid(), entity.getUuid()); + dbClient.userPermissionDao().insert(dbSession, dto, entity, userDto, template); + }); + } + } + + private static boolean permissionValidForProject(boolean isPrivateEntity, String permission) { + return isPrivateEntity || !PUBLIC_PERMISSIONS.contains(permission); + } + + private static boolean groupNameValidForProject(boolean isPrivateEntity, String groupName) { + return !isPrivateEntity || !isAnyone(groupName); + } + + /** + * Return the permission template for the given component. If no template key pattern match then consider default + * template for the component qualifier. + */ + @CheckForNull + private PermissionTemplateDto findTemplate(DbSession dbSession, EntityDto entityDto) { + List allPermissionTemplates = dbClient.permissionTemplateDao().selectAll(dbSession, null); + List matchingTemplates = new ArrayList<>(); + for (PermissionTemplateDto permissionTemplateDto : allPermissionTemplates) { + String keyPattern = permissionTemplateDto.getKeyPattern(); + if (StringUtils.isNotBlank(keyPattern) && entityDto.getKey().matches(keyPattern)) { + matchingTemplates.add(permissionTemplateDto); + } + } + checkAtMostOneMatchForComponentKey(entityDto.getKey(), matchingTemplates); + if (matchingTemplates.size() == 1) { + return matchingTemplates.get(0); + } + + String qualifier = entityDto.getQualifier(); + DefaultTemplatesResolver.ResolvedDefaultTemplates resolvedDefaultTemplates = defaultTemplatesResolver.resolve(dbSession); + switch (qualifier) { + case Qualifiers.PROJECT: + return dbClient.permissionTemplateDao().selectByUuid(dbSession, resolvedDefaultTemplates.getProject()); + case Qualifiers.VIEW: + String portDefaultTemplateUuid = resolvedDefaultTemplates.getPortfolio().orElseThrow( + () -> new IllegalStateException("Failed to find default template for portfolios")); + return dbClient.permissionTemplateDao().selectByUuid(dbSession, portDefaultTemplateUuid); + case Qualifiers.APP: + String appDefaultTemplateUuid = resolvedDefaultTemplates.getApplication().orElseThrow( + () -> new IllegalStateException("Failed to find default template for applications")); + return dbClient.permissionTemplateDao().selectByUuid(dbSession, appDefaultTemplateUuid); + default: + throw new IllegalArgumentException(format("Qualifier '%s' is not supported", qualifier)); + } + } + + private static void checkAtMostOneMatchForComponentKey(String componentKey, List matchingTemplates) { + if (matchingTemplates.size() > 1) { + StringBuilder templatesNames = new StringBuilder(); + for (Iterator it = matchingTemplates.iterator(); it.hasNext(); ) { + templatesNames.append("\"").append(it.next().getName()).append("\""); + if (it.hasNext()) { + templatesNames.append(", "); + } + } + throw new TemplateMatchingKeyException(MessageFormat.format( + "The \"{0}\" key matches multiple permission templates: {1}." + + " A system administrator must update these templates so that only one of them matches the key.", + componentKey, + templatesNames.toString())); + } + } + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/PermissionUpdater.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/PermissionUpdater.java new file mode 100644 index 00000000000..e2beb971b46 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/PermissionUpdater.java @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.permission; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import org.sonar.db.DbSession; +import org.sonar.db.entity.EntityDto; +import org.sonar.server.es.Indexers; + +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toMap; +import static org.sonar.api.utils.Preconditions.checkState; +import static org.sonar.server.es.Indexers.EntityEvent.PERMISSION_CHANGE; + +public class PermissionUpdater { + + private final Indexers indexers; + + private final Map, GranteeTypeSpecificPermissionUpdater> specificPermissionClassToHandler; + + public PermissionUpdater(Indexers indexers, Set> permissionChangers) { + this.indexers = indexers; + specificPermissionClassToHandler = permissionChangers.stream() + .collect(toMap(GranteeTypeSpecificPermissionUpdater::getHandledClass, Function.identity())); + } + + public void apply(DbSession dbSession, Collection changes) { + checkState(changes.stream().map(PermissionChange::getProjectUuid).distinct().count() <= 1, + "Only one project per changes is supported"); + + List projectOrViewUuids = new ArrayList<>(); + Map, List> granteeUuidToPermissionChanges = changes.stream().collect(groupingBy(change -> Optional.ofNullable(change.getUuidOfGrantee()))); + granteeUuidToPermissionChanges.values().forEach(permissionChanges -> applyForSingleGrantee(dbSession, projectOrViewUuids, permissionChanges)); + + indexers.commitAndIndexOnEntityEvent(dbSession, projectOrViewUuids, PERMISSION_CHANGE); + } + + private void applyForSingleGrantee(DbSession dbSession, List projectOrViewUuids, List permissionChanges) { + T anyPermissionChange = permissionChanges.iterator().next(); + EntityDto entity = anyPermissionChange.getEntity(); + String entityUuid = Optional.ofNullable(entity).map(EntityDto::getUuid).orElse(null); + GranteeTypeSpecificPermissionUpdater granteeTypeSpecificPermissionUpdater = getSpecificProjectUpdater(anyPermissionChange); + Set existingPermissions = granteeTypeSpecificPermissionUpdater.loadExistingEntityPermissions(dbSession, anyPermissionChange.getUuidOfGrantee(), entityUuid); + for (T permissionChange : permissionChanges) { + if (granteeTypeSpecificPermissionUpdater.apply(dbSession, existingPermissions, permissionChange) && permissionChange.getProjectUuid() != null) { + projectOrViewUuids.add(permissionChange.getProjectUuid()); + } + } + } + + private GranteeTypeSpecificPermissionUpdater getSpecificProjectUpdater(T anyPermissionChange) { + return specificPermissionClassToHandler.get(anyPermissionChange.getClass()); + } + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/UserPermissionChange.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/UserPermissionChange.java new file mode 100644 index 00000000000..acb5c260065 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/UserPermissionChange.java @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.permission; + +import javax.annotation.Nullable; +import org.sonar.db.entity.EntityDto; +import org.sonar.db.user.UserId; +import org.sonar.server.common.permission.Operation; +import org.sonar.server.permission.PermissionService; + +import static java.util.Objects.requireNonNull; + +public class UserPermissionChange extends PermissionChange { + + private final UserId userId; + + public UserPermissionChange(Operation operation, String permission, @Nullable EntityDto entity, UserId userId, + PermissionService permissionService) { + super(operation, permission, entity, permissionService); + this.userId = requireNonNull(userId); + } + + public UserId getUserId() { + return userId; + } + + @Override + public String getUuidOfGrantee() { + return userId.getUuid(); + } +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/UserPermissionChanger.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/UserPermissionChanger.java new file mode 100644 index 00000000000..3874f7b1f48 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/UserPermissionChanger.java @@ -0,0 +1,142 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.permission; + +import java.util.HashSet; +import java.util.Set; +import org.jetbrains.annotations.Nullable; +import org.sonar.api.web.UserRole; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.entity.EntityDto; +import org.sonar.db.permission.GlobalPermission; +import org.sonar.db.permission.UserPermissionDto; + +import static org.sonar.server.common.permission.Operation.ADD; +import static org.sonar.server.common.permission.Operation.REMOVE; +import static org.sonar.server.exceptions.BadRequestException.checkRequest; + +/** + * Adds and removes user permissions. Both global and project scopes are supported. + */ +public class UserPermissionChanger implements GranteeTypeSpecificPermissionUpdater { + + private final DbClient dbClient; + private final UuidFactory uuidFactory; + + public UserPermissionChanger(DbClient dbClient, UuidFactory uuidFactory) { + this.dbClient = dbClient; + this.uuidFactory = uuidFactory; + } + + @Override + public Class getHandledClass() { + return UserPermissionChange.class; + } + + @Override + public Set loadExistingEntityPermissions(DbSession dbSession, String uuidOfGrantee, @Nullable String entityUuid) { + if (entityUuid != null) { + return new HashSet<>(dbClient.userPermissionDao().selectEntityPermissionsOfUser(dbSession, uuidOfGrantee, entityUuid)); + } + return new HashSet<>(dbClient.userPermissionDao().selectGlobalPermissionsOfUser(dbSession, uuidOfGrantee)); + } + + @Override + public boolean apply(DbSession dbSession, Set existingPermissions, UserPermissionChange change) { + ensureConsistencyWithVisibility(change); + if (isImplicitlyAlreadyDone(change)) { + return false; + } + switch (change.getOperation()) { + case ADD: + return addPermission(dbSession, existingPermissions, change); + case REMOVE: + return removePermission(dbSession, existingPermissions, change); + default: + throw new UnsupportedOperationException("Unsupported permission change: " + change.getOperation()); + } + } + + private static boolean isImplicitlyAlreadyDone(UserPermissionChange change) { + EntityDto project = change.getEntity(); + if (project != null) { + return isImplicitlyAlreadyDone(project, change); + } + return false; + } + + private static boolean isImplicitlyAlreadyDone(EntityDto project, UserPermissionChange change) { + return isAttemptToAddPublicPermissionToPublicComponent(change, project); + } + + private static boolean isAttemptToAddPublicPermissionToPublicComponent(UserPermissionChange change, EntityDto project) { + return !project.isPrivate() + && change.getOperation() == ADD + && UserRole.PUBLIC_PERMISSIONS.contains(change.getPermission()); + } + + private static void ensureConsistencyWithVisibility(UserPermissionChange change) { + EntityDto project = change.getEntity(); + if (project != null) { + checkRequest(!isAttemptToRemovePublicPermissionFromPublicComponent(change, project), + "Permission %s can't be removed from a public component", change.getPermission()); + } + } + + private static boolean isAttemptToRemovePublicPermissionFromPublicComponent(UserPermissionChange change, EntityDto entity) { + return !entity.isPrivate() + && change.getOperation() == REMOVE + && UserRole.PUBLIC_PERMISSIONS.contains(change.getPermission()); + } + + private boolean addPermission(DbSession dbSession, Set existingPermissions, UserPermissionChange change) { + if (existingPermissions.contains(change.getPermission())) { + return false; + } + UserPermissionDto dto = new UserPermissionDto(uuidFactory.create(), change.getPermission(), change.getUserId().getUuid(), + change.getProjectUuid()); + dbClient.userPermissionDao().insert(dbSession, dto, change.getEntity(), change.getUserId(), null); + return true; + } + + private boolean removePermission(DbSession dbSession, Set existingPermissions, UserPermissionChange change) { + if (!existingPermissions.contains(change.getPermission())) { + return false; + } + checkOtherAdminsExist(dbSession, change); + EntityDto entity = change.getEntity(); + if (entity != null) { + dbClient.userPermissionDao().deleteEntityPermission(dbSession, change.getUserId(), change.getPermission(), entity); + } else { + dbClient.userPermissionDao().deleteGlobalPermission(dbSession, change.getUserId(), change.getPermission()); + } + return true; + } + + private void checkOtherAdminsExist(DbSession dbSession, UserPermissionChange change) { + if (GlobalPermission.ADMINISTER.getKey().equals(change.getPermission()) && change.getProjectUuid() == null) { + int remaining = dbClient.authorizationDao().countUsersWithGlobalPermissionExcludingUserPermission(dbSession, change.getPermission(), change.getUserId().getUuid()); + checkRequest(remaining > 0, "Last user with permission '%s'. Permission cannot be removed.", GlobalPermission.ADMINISTER.getKey()); + } + } + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ProjectCreator.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ProjectCreator.java new file mode 100644 index 00000000000..22cf07233f4 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ProjectCreator.java @@ -0,0 +1,71 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.project; + +import javax.annotation.Nullable; +import org.sonar.api.server.ServerSide; +import org.sonar.db.DbSession; +import org.sonar.db.project.CreationMethod; +import org.sonar.server.common.component.ComponentCreationParameters; +import org.sonar.server.common.component.ComponentUpdater; +import org.sonar.server.common.component.NewComponent; +import org.sonar.server.component.ComponentCreationData; +import org.sonar.server.project.ProjectDefaultVisibility; +import org.sonar.server.user.UserSession; + +import static org.sonar.api.resources.Qualifiers.PROJECT; + +@ServerSide +public class ProjectCreator { + + private final UserSession userSession; + private final ProjectDefaultVisibility projectDefaultVisibility; + private final ComponentUpdater componentUpdater; + + public ProjectCreator(UserSession userSession, ProjectDefaultVisibility projectDefaultVisibility, ComponentUpdater componentUpdater) { + this.userSession = userSession; + this.projectDefaultVisibility = projectDefaultVisibility; + this.componentUpdater = componentUpdater; + } + + public ComponentCreationData createProject(DbSession dbSession, String projectKey, String projectName, @Nullable String mainBranchName, CreationMethod creationMethod, + @Nullable Boolean isPrivate, boolean isManaged) { + boolean visibility = isPrivate != null ? isPrivate : projectDefaultVisibility.get(dbSession).isPrivate(); + NewComponent projectComponent = NewComponent.newComponentBuilder() + .setKey(projectKey) + .setName(projectName) + .setPrivate(visibility) + .setQualifier(PROJECT) + .build(); + ComponentCreationParameters componentCreationParameters = ComponentCreationParameters.builder() + .newComponent(projectComponent) + .userLogin(userSession.getLogin()) + .userUuid(userSession.getUuid()) + .mainBranchName(mainBranchName) + .isManaged(isManaged) + .creationMethod(creationMethod) + .build(); + return componentUpdater.createWithoutCommit(dbSession, componentCreationParameters); + } + + public ComponentCreationData createProject(DbSession dbSession, String projectKey, String projectName, @Nullable String mainBranchName, CreationMethod creationMethod) { + return createProject(dbSession, projectKey, projectName, mainBranchName, creationMethod, projectDefaultVisibility.get(dbSession).isPrivate(), false); + } +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/package-info.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/package-info.java new file mode 100644 index 00000000000..174ee7fd7cb --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.common.project; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almintegration/ProjectKeyGeneratorTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almintegration/ProjectKeyGeneratorTest.java new file mode 100644 index 00000000000..3751d757bda --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almintegration/ProjectKeyGeneratorTest.java @@ -0,0 +1,92 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almintegration; + +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.sonar.core.util.UuidFactory; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; +import static org.sonar.server.common.almintegration.ProjectKeyGenerator.MAX_PROJECT_KEY_SIZE; +import static org.sonar.server.common.almintegration.ProjectKeyGenerator.PROJECT_KEY_SEPARATOR; + +@RunWith(MockitoJUnitRunner.class) +public class ProjectKeyGeneratorTest { + + private static final int MAX_UUID_SIZE = 40; + private static final String UUID_STRING = RandomStringUtils.randomAlphanumeric(MAX_UUID_SIZE); + + @Mock + private UuidFactory uuidFactory; + + @InjectMocks + private ProjectKeyGenerator projectKeyGenerator; + + @Before + public void setUp() { + when(uuidFactory.create()).thenReturn(UUID_STRING); + } + + @Test + public void generateUniqueProjectKey_shortProjectName_shouldAppendUuid() { + String fullProjectName = RandomStringUtils.randomAlphanumeric(10); + + assertThat(projectKeyGenerator.generateUniqueProjectKey(fullProjectName)) + .isEqualTo(generateExpectedKeyName(fullProjectName)); + } + + @Test + public void generateUniqueProjectKey_projectNameEqualsToMaximumSize_shouldTruncateProjectNameAndPreserveUUID() { + String fullProjectName = RandomStringUtils.randomAlphanumeric(MAX_PROJECT_KEY_SIZE); + + String projectKey = projectKeyGenerator.generateUniqueProjectKey(fullProjectName); + assertThat(projectKey) + .hasSize(MAX_PROJECT_KEY_SIZE) + .isEqualTo(generateExpectedKeyName(fullProjectName.substring(fullProjectName.length() + UUID_STRING.length() + 1 - MAX_PROJECT_KEY_SIZE))); + } + + @Test + public void generateUniqueProjectKey_projectNameBiggerThanMaximumSize_shouldTruncateProjectNameAndPreserveUUID() { + String fullProjectName = RandomStringUtils.randomAlphanumeric(MAX_PROJECT_KEY_SIZE + 50); + + String projectKey = projectKeyGenerator.generateUniqueProjectKey(fullProjectName); + assertThat(projectKey) + .hasSize(MAX_PROJECT_KEY_SIZE) + .isEqualTo(generateExpectedKeyName(fullProjectName.substring(fullProjectName.length() + UUID_STRING.length() + 1 - MAX_PROJECT_KEY_SIZE))); + } + + @Test + public void generateUniqueProjectKey_projectNameContainsSlashes_shouldBeEscaped() { + String fullProjectName = "a/b/c"; + + assertThat(projectKeyGenerator.generateUniqueProjectKey(fullProjectName)) + .isEqualTo(generateExpectedKeyName(fullProjectName.replace("/", "_"))); + } + + private String generateExpectedKeyName(String truncatedProjectName) { + return truncatedProjectName + PROJECT_KEY_SEPARATOR + UUID_STRING; + } +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/DelegatingDevOpsProjectCreatorFactoryTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/DelegatingDevOpsProjectCreatorFactoryTest.java new file mode 100644 index 00000000000..b8815607956 --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/DelegatingDevOpsProjectCreatorFactoryTest.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import org.junit.Test; +import org.sonar.db.DbSession; + +import static java.util.Collections.emptySet; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DelegatingDevOpsProjectCreatorFactoryTest { + + private static final DbSession DB_SESSION = mock(); + private static final Map CHARACTERISTICS = Map.of("toto", "tata"); + + @Test + public void getDevOpsProjectDescriptor_whenNoDelegates_shouldReturnEmptyOptional() { + DelegatingDevOpsProjectCreatorFactory noDelegates = new DelegatingDevOpsProjectCreatorFactory(emptySet()); + Optional devOpsProjectCreator = noDelegates.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS); + assertThat(devOpsProjectCreator).isEmpty(); + } + + @Test + public void getDevOpsProjectDescriptor_whenNoDelegatesReturningACreator_shouldReturnEmptyOptional() { + DelegatingDevOpsProjectCreatorFactory delegates = new DelegatingDevOpsProjectCreatorFactory(Set.of(mock(), mock())); + Optional devOpsProjectCreator = delegates.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS); + + assertThat(devOpsProjectCreator).isEmpty(); + } + + @Test + public void getDevOpsProjectDescriptor_whenOneDelegatesReturningACreator_shouldDelegate() { + DevOpsProjectCreatorFactory successfulDelegate = mock(); + DevOpsProjectCreator devOpsProjectCreator = mock(); + when(successfulDelegate.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS)).thenReturn(Optional.of(devOpsProjectCreator)); + DelegatingDevOpsProjectCreatorFactory delegates = new DelegatingDevOpsProjectCreatorFactory(Set.of(mock(), successfulDelegate)); + + assertThat(delegates.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS)).contains(devOpsProjectCreator); + } + +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorFactoryTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorFactoryTest.java new file mode 100644 index 00000000000..ac805ea74ab --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorFactoryTest.java @@ -0,0 +1,269 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings.github; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.sonar.alm.client.github.GithubGlobalSettingsValidator; +import org.sonar.alm.client.github.GithubPermissionConverter; +import org.sonar.auth.github.AppInstallationToken; +import org.sonar.auth.github.GitHubSettings; +import org.sonar.auth.github.client.GithubApplicationClient; +import org.sonar.auth.github.security.AccessToken; +import org.sonar.auth.github.security.UserAccessToken; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.pat.AlmPatDto; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.almsettings.DevOpsProjectCreator; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.common.almsettings.github.GithubProjectCreationParameters; +import org.sonar.server.common.almsettings.github.GithubProjectCreator; +import org.sonar.server.common.almsettings.github.GithubProjectCreatorFactory; +import org.sonar.server.exceptions.BadConfigurationException; +import org.sonar.server.management.ManagedProjectService; +import org.sonar.server.permission.PermissionService; +import org.sonar.server.common.permission.PermissionUpdater; +import org.sonar.server.common.permission.UserPermissionChange; +import org.sonar.server.project.ProjectDefaultVisibility; +import org.sonar.server.common.project.ProjectCreator; +import org.sonar.server.user.UserSession; + +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.core.ce.CeTaskCharacteristics.DEVOPS_PLATFORM_PROJECT_IDENTIFIER; +import static org.sonar.core.ce.CeTaskCharacteristics.DEVOPS_PLATFORM_URL; + +@RunWith(MockitoJUnitRunner.class) +public class GithubProjectCreatorFactoryTest { + private static final String PROJECT_NAME = "projectName"; + private static final String ORGANIZATION_NAME = "orgname"; + private static final String GITHUB_REPO_FULL_NAME = ORGANIZATION_NAME + "/" + PROJECT_NAME; + private static final String GITHUB_API_URL = "https://api.toto.com"; + + private static final DevOpsProjectDescriptor GITHUB_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITHUB, GITHUB_API_URL, GITHUB_REPO_FULL_NAME); + private static final Map VALID_GITHUB_PROJECT_COORDINATES = Map.of( + DEVOPS_PLATFORM_URL, GITHUB_PROJECT_DESCRIPTOR.url(), + DEVOPS_PLATFORM_PROJECT_IDENTIFIER, GITHUB_PROJECT_DESCRIPTOR.projectIdentifier()); + private static final long APP_INSTALLATION_ID = 534534534543L; + private static final String USER_ACCESS_TOKEN = "userPat"; + + @Mock + private DbSession dbSession; + @Mock + private GithubGlobalSettingsValidator githubGlobalSettingsValidator; + @Mock + private GithubApplicationClient githubApplicationClient; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private DbClient dbClient; + @Mock + private UserSession userSession; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private ProjectDefaultVisibility projectDefaultVisibility; + @Mock + private ProjectKeyGenerator projectKeyGenerator; + @Mock + private GitHubSettings gitHubSettings; + @Mock + private GithubPermissionConverter githubPermissionConverter; + @Mock + private AppInstallationToken appInstallationToken; + @Mock + private AppInstallationToken authAppInstallationToken; + @Mock + private PermissionService permissionService; + @Mock + private PermissionUpdater permissionUpdater; + @Mock + private ManagedProjectService managedProjectService; + @Mock + private ProjectCreator projectCreator; + + @InjectMocks + private GithubProjectCreatorFactory githubProjectCreatorFactory; + + @Test + public void getDevOpsProjectCreator_whenNoCharacteristics_shouldReturnEmpty() { + Optional devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, Map.of()); + + assertThat(devOpsProjectCreator).isEmpty(); + } + + @Test + public void getDevOpsProjectCreator_whenValidCharacteristicsButNoAlmSettingDao_shouldReturnEmpty() { + Optional devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES); + assertThat(devOpsProjectCreator).isEmpty(); + } + + @Test + public void getDevOpsProjectCreator_whenValidCharacteristicsButInvalidAlmSettingDto_shouldThrow() { + AlmSettingDto almSettingDto = mockAlmSettingDto(true); + IllegalArgumentException error = new IllegalArgumentException("error happened"); + when(githubGlobalSettingsValidator.validate(almSettingDto)).thenThrow(error); + + assertThatIllegalArgumentException().isThrownBy(() -> githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES)) + .isSameAs(error); + } + + @Test + public void getDevOpsProjectCreator_whenAppHasNoAccessToRepo_shouldReturnEmpty() { + mockAlmSettingDto(true); + when(githubApplicationClient.getInstallationId(any(), eq(GITHUB_REPO_FULL_NAME))).thenReturn(Optional.empty()); + + Optional devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES); + assertThat(devOpsProjectCreator).isEmpty(); + } + + @Test + public void getDevOpsProjectCreator_whenNotPossibleToGenerateToken_shouldThrow() { + AlmSettingDto almSettingDto = mockAlmSettingDto(true); + when(githubApplicationClient.getInstallationId(any(), eq(GITHUB_REPO_FULL_NAME))).thenReturn(Optional.of(APP_INSTALLATION_ID)); + when(githubGlobalSettingsValidator.validate(almSettingDto)).thenReturn(mock()); + when(githubApplicationClient.createAppInstallationToken(any(), eq(APP_INSTALLATION_ID))).thenReturn(Optional.empty()); + + assertThatIllegalStateException().isThrownBy(() -> githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES)) + .withMessage("Error while generating token for GitHub Api Url null (installation id: 534534534543)"); + } + + @Test + public void getDevOpsProjectCreator_whenOneValidAlmSetting_shouldInstantiateDevOpsProjectCreator() { + AlmSettingDto almSettingDto = mockAlmSettingDto(true); + mockSuccessfulGithubInteraction(); + + DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES).orElseThrow(); + + GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(almSettingDto, false, appInstallationToken); + assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedGithubProjectCreator); + } + + @Test + public void getDevOpsProjectCreator_whenOneValidAlmSettingAndPublicByDefaultAndAutoProvisioningEnabled_shouldInstantiateDevOpsProjectCreatorAndDefineAnAuthAppToken() { + AlmSettingDto almSettingDto = mockAlmSettingDto(true); + mockSuccessfulGithubInteraction(); + + when(projectDefaultVisibility.get(any()).isPrivate()).thenReturn(true); + mockValidGitHubSettings(); + + 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(); + + GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(almSettingDto, true, appInstallationToken); + assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedGithubProjectCreator); + } + + @Test + public void getDevOpsProjectCreator_whenOneMatchingAndOneNotMatchingAlmSetting_shouldInstantiateDevOpsProjectCreator() { + AlmSettingDto matchingAlmSettingDto = mockAlmSettingDto(true); + AlmSettingDto notMatchingAlmSettingDto = mockAlmSettingDto(false); + when(dbClient.almSettingDao().selectByAlm(dbSession, ALM.GITHUB)).thenReturn(List.of(notMatchingAlmSettingDto, matchingAlmSettingDto)); + + mockSuccessfulGithubInteraction(); + + DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES).orElseThrow(); + + GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(matchingAlmSettingDto, false, appInstallationToken); + assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedGithubProjectCreator); + } + + @Test + public void getDevOpsProjectCreatorFromImport_shouldInstantiateDevOpsProjectCreator() { + AlmSettingDto mockAlmSettingDto = mockAlmSettingDto(true); + mockAlmPatDto(mockAlmSettingDto); + + mockSuccessfulGithubInteraction(); + + DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(mockAlmSettingDto, GITHUB_PROJECT_DESCRIPTOR).orElseThrow(); + + GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(mockAlmSettingDto, false, new UserAccessToken(USER_ACCESS_TOKEN)); + assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedGithubProjectCreator); + } + + @Test + public void getDevOpsProjectCreatorFromImport_whenGitHubConfigDoesNotAllowAccessToRepo_shouldThrow() { + AlmSettingDto mockAlmSettingDto = mockAlmSettingDto(false); + mockAlmPatDto(mockAlmSettingDto); + + mockValidGitHubSettings(); + + when(githubApplicationClient.getInstallationId(any(), eq(GITHUB_REPO_FULL_NAME))).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> githubProjectCreatorFactory.getDevOpsProjectCreator(mockAlmSettingDto, GITHUB_PROJECT_DESCRIPTOR)) + .isInstanceOf(BadConfigurationException.class) + .hasMessage(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 created.", + GITHUB_REPO_FULL_NAME)); + } + + private void mockValidGitHubSettings() { + when(gitHubSettings.appId()).thenReturn("4324"); + when(gitHubSettings.privateKey()).thenReturn("privateKey"); + when(gitHubSettings.apiURL()).thenReturn(GITHUB_API_URL); + when(gitHubSettings.isProvisioningEnabled()).thenReturn(true); + } + + 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 isInstanceManaged, AccessToken accessToken) { + DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, almSettingDto.getUrl(), GITHUB_REPO_FULL_NAME); + AppInstallationToken authAppInstallToken = isInstanceManaged ? authAppInstallationToken : null; + GithubProjectCreationParameters githubProjectCreationParameters = new GithubProjectCreationParameters(devOpsProjectDescriptor, almSettingDto, userSession, accessToken, + authAppInstallToken); + return new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, permissionUpdater, permissionService, + managedProjectService, projectCreator, githubProjectCreationParameters, gitHubSettings); + } + + private AlmSettingDto mockAlmSettingDto(boolean repoAccess) { + AlmSettingDto almSettingDto = mock(); + when(almSettingDto.getUrl()).thenReturn(repoAccess ? GITHUB_PROJECT_DESCRIPTOR.url() : "anotherUrl"); + when(almSettingDto.getAlm()).thenReturn(ALM.GITHUB); + + when(dbClient.almSettingDao().selectByAlm(dbSession, ALM.GITHUB)).thenReturn(List.of(almSettingDto)); + return almSettingDto; + } + + private void mockAlmPatDto(AlmSettingDto almSettingDto) { + when(userSession.getUuid()).thenReturn("userUuid"); + when(dbClient.almPatDao().selectByUserAndAlmSetting(any(), eq("userUuid"), eq(almSettingDto))) + .thenReturn(Optional.of(new AlmPatDto().setPersonalAccessToken(USER_ACCESS_TOKEN))); + } + +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorTest.java new file mode 100644 index 00000000000..dcff559a270 --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorTest.java @@ -0,0 +1,478 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings.github; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Optional; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Answers; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.sonar.alm.client.github.GithubPermissionConverter; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.web.UserRole; +import org.sonar.auth.github.AppInstallationToken; +import org.sonar.auth.github.GitHubSettings; +import org.sonar.auth.github.GsonRepositoryCollaborator; +import org.sonar.auth.github.GsonRepositoryPermissions; +import org.sonar.auth.github.GsonRepositoryTeam; +import org.sonar.auth.github.client.GithubApplicationClient; +import org.sonar.auth.github.security.AccessToken; +import org.sonar.db.DbClient; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDao; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; +import org.sonar.db.component.BranchDto; +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.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.common.component.ComponentCreationParameters; +import org.sonar.server.common.component.ComponentUpdater; +import org.sonar.server.common.component.NewComponent; +import org.sonar.server.common.permission.PermissionUpdater; +import org.sonar.server.common.permission.UserPermissionChange; +import org.sonar.server.common.project.ProjectCreator; +import org.sonar.server.component.ComponentCreationData; +import org.sonar.server.management.ManagedProjectService; +import org.sonar.server.permission.PermissionService; +import org.sonar.server.permission.PermissionServiceImpl; +import org.sonar.server.project.ProjectDefaultVisibility; +import org.sonar.server.project.Visibility; +import org.sonar.server.user.UserSession; + +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toSet; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.sonar.db.project.CreationMethod.ALM_IMPORT_API; +import static org.sonar.db.project.CreationMethod.SCANNER_API_DEVOPS_AUTO_CONFIG; + +@ExtendWith(MockitoExtension.class) +class GithubProjectCreatorTest { + + private static final String ORGANIZATION_NAME = "orga2"; + private static final String REPOSITORY_NAME = "repo1"; + + private static final String MAIN_BRANCH_NAME = "defaultBranch"; + private static final DevOpsProjectDescriptor DEVOPS_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITHUB, "http://api.com", ORGANIZATION_NAME + "/" + REPOSITORY_NAME); + private static final String ALM_SETTING_KEY = "github_config_1"; + private static final String USER_LOGIN = "userLogin"; + private static final String USER_UUID = "userUuid"; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private DbClient dbClient; + @Mock + private GithubApplicationClient githubApplicationClient; + @Mock + private GithubPermissionConverter githubPermissionConverter; + @Mock + private ProjectKeyGenerator projectKeyGenerator; + @Mock + private ComponentUpdater componentUpdater; + @Mock + private GithubProjectCreationParameters githubProjectCreationParameters; + @Mock + private AccessToken devOpsAppInstallationToken; + @Mock + private AppInstallationToken authAppInstallationToken; + @Mock + private UserSession userSession; + @Mock + private AlmSettingDto almSettingDto; + private final PermissionService permissionService = new PermissionServiceImpl(mock()); + @Mock + private PermissionUpdater permissionUpdater; + @Mock + private ManagedProjectService managedProjectService; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private ProjectDefaultVisibility projectDefaultVisibility; + private final GitHubSettings gitHubSettings = mock(); + + private GithubProjectCreator githubProjectCreator; + + @Captor + ArgumentCaptor componentCreationParametersCaptor; + @Captor + ArgumentCaptor projectAlmSettingDtoCaptor; + + @BeforeEach + void setup() { + lenient().when(userSession.getLogin()).thenReturn(USER_LOGIN); + lenient().when(userSession.getUuid()).thenReturn(USER_UUID); + + lenient().when(almSettingDto.getUrl()).thenReturn(DEVOPS_PROJECT_DESCRIPTOR.url()); + lenient().when(almSettingDto.getKey()).thenReturn(ALM_SETTING_KEY); + + when(githubProjectCreationParameters.devOpsProjectDescriptor()).thenReturn(DEVOPS_PROJECT_DESCRIPTOR); + when(githubProjectCreationParameters.userSession()).thenReturn(userSession); + when(githubProjectCreationParameters.devOpsAppInstallationToken()).thenReturn(devOpsAppInstallationToken); + when(githubProjectCreationParameters.authAppInstallationToken()).thenReturn(authAppInstallationToken); + when(githubProjectCreationParameters.almSettingDto()).thenReturn(almSettingDto); + + ProjectCreator projectCreator = new ProjectCreator(userSession, projectDefaultVisibility, componentUpdater); + githubProjectCreator = new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, + permissionUpdater, permissionService, managedProjectService, projectCreator, githubProjectCreationParameters, gitHubSettings); + + } + + @Test + 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 + void isScanAllowedUsingPermissionsFromDevopsPlatform_whenUserIsNotAGitHubUser_returnsFalse() { + assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isFalse(); + } + + @Test + void isScanAllowedUsingPermissionsFromDevopsPlatform_whenCollaboratorHasDirectAccessButNoScanPermissions_returnsFalse() { + GsonRepositoryCollaborator collaborator1 = mockCollaborator("collaborator1", 1, "role1", "read", "admin"); + mockGithubCollaboratorsFromApi(collaborator1); + bindSessionToCollaborator(collaborator1); + + assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isFalse(); + } + + @Test + void isScanAllowedUsingPermissionsFromDevopsPlatform_whenCollaboratorHasDirectAccess_returnsTrue() { + GsonRepositoryCollaborator collaborator1 = mockCollaborator("collaborator1", 1, "role1", "read", "admin"); + GsonRepositoryCollaborator collaborator2 = mockCollaborator("collaborator2", 2, "role2", "read", "scan"); + mockGithubCollaboratorsFromApi(collaborator1, collaborator2); + bindSessionToCollaborator(collaborator2); + + assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isTrue(); + } + + @Test + void isScanAllowedUsingPermissionsFromDevopsPlatform_whenAccessViaTeamButNoScanPermissions_returnsFalse() { + GsonRepositoryTeam team2 = mockGithubTeam("team2", 2, "role2", "another_perm", UserRole.ADMIN); + mockTeamsFromApi(team2); + bindGroupsToUser(team2.name()); + + assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isFalse(); + } + + @Test + void isScanAllowedUsingPermissionsFromDevopsPlatform_whenAccessViaTeam_returnsTrue() { + GsonRepositoryTeam team1 = mockGithubTeam("team1", 1, "role1", "read", "another_perm"); + GsonRepositoryTeam team2 = mockGithubTeam("team2", 2, "role2", "another_perm", UserRole.SCAN); + mockTeamsFromApi(team1, team2); + bindGroupsToUser(team1.name(), team2.name()); + + assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isTrue(); + } + + @Test + void isScanAllowedUsingPermissionsFromDevopsPlatform_whenAccessViaTeamButUserNotInTeam_returnsFalse() { + GsonRepositoryTeam team1 = mockGithubTeam("team1", 1, "role1", "read", "another_perm"); + GsonRepositoryTeam team2 = mockGithubTeam("team2", 2, "role2", "another_perm", UserRole.SCAN); + mockTeamsFromApi(team1, team2); + bindGroupsToUser(team1.name()); + + assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isFalse(); + } + + private void bindSessionToCollaborator(GsonRepositoryCollaborator collaborator1) { + UserSession.ExternalIdentity externalIdentity = new UserSession.ExternalIdentity(String.valueOf(collaborator1.id()), collaborator1.name()); + when(userSession.getExternalIdentity()).thenReturn(Optional.of(externalIdentity)); + } + + private GsonRepositoryCollaborator mockCollaborator(String collaboratorLogin, int id, String role1, String... sqPermissions) { + GsonRepositoryCollaborator collaborator = new GsonRepositoryCollaborator(collaboratorLogin, id, role1, + new GsonRepositoryPermissions(false, false, false, false, false)); + mockPermissionsConversion(collaborator, sqPermissions); + return collaborator; + } + + private void mockGithubCollaboratorsFromApi(GsonRepositoryCollaborator... repositoryCollaborators) { + Set collaborators = Arrays.stream(repositoryCollaborators).collect(toSet()); + 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) { + GsonRepositoryTeam gsonRepositoryTeam = new GsonRepositoryTeam(name, id, name + "slug", role, new GsonRepositoryPermissions(false, false, false, false, false)); + mockPermissionsConversion(gsonRepositoryTeam, sqPermissions); + return gsonRepositoryTeam; + } + + private void mockTeamsFromApi(GsonRepositoryTeam... repositoryTeams) { + when(githubApplicationClient.getRepositoryTeams(DEVOPS_PROJECT_DESCRIPTOR.url(), authAppInstallationToken, ORGANIZATION_NAME, REPOSITORY_NAME)) + .thenReturn(Arrays.stream(repositoryTeams).collect(toSet())); + } + + private void mockPermissionsConversion(GsonRepositoryCollaborator collaborator, String... sqPermissions) { + Set githubPermissionsMappingDtos = mockPermissionsMappingsDtos(); + lenient().when(githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(githubPermissionsMappingDtos, collaborator.roleName(), collaborator.permissions())) + .thenReturn(Arrays.stream(sqPermissions).collect(toSet())); + } + + private void mockPermissionsConversion(GsonRepositoryTeam team, String... sqPermissions) { + Set githubPermissionsMappingDtos = mockPermissionsMappingsDtos(); + lenient().when(githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(githubPermissionsMappingDtos, team.permission(), team.permissions())) + .thenReturn(Arrays.stream(sqPermissions).collect(toSet())); + } + + private Set mockPermissionsMappingsDtos() { + Set githubPermissionsMappingDtos = Set.of(mock(GithubPermissionsMappingDto.class)); + when(dbClient.githubPermissionsMappingDao().findAll(any())).thenReturn(githubPermissionsMappingDtos); + return githubPermissionsMappingDtos; + } + + private void bindGroupsToUser(String... groupNames) { + Set groupDtos = Arrays.stream(groupNames) + .map(groupName -> new GroupDto().setName(ORGANIZATION_NAME + "/" + groupName).setUuid("uuid_" + groupName)) + .collect(toSet()); + when(userSession.getGroups()).thenReturn(groupDtos); + } + + @Test + void createProjectAndBindToDevOpsPlatform_whenRepoNotFound_throws() { + assertThatIllegalStateException().isThrownBy( + () -> githubProjectCreator.createProjectAndBindToDevOpsPlatform(mock(), SCANNER_API_DEVOPS_AUTO_CONFIG, false, null, null)) + .withMessage("Impossible to find the repository 'orga2/repo1' on GitHub, using the devops config " + ALM_SETTING_KEY); + } + + @Test + void createProjectAndBindToDevOpsPlatformFromScanner_whenRepoFoundOnGitHub_successfullyCreatesProject() { + // given + mockGitHubRepository(); + + ComponentCreationData componentCreationData = mockProjectCreation("generated_orga2/repo1"); + ProjectAlmSettingDao projectAlmSettingDao = mock(); + when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); + when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE); + + // when + ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), + SCANNER_API_DEVOPS_AUTO_CONFIG, false, null, null); + + // then + assertThat(actualComponentCreationData).isEqualTo(componentCreationData); + + ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); + assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters, "generated_orga2/repo1", SCANNER_API_DEVOPS_AUTO_CONFIG); + assertThat(componentCreationParameters.isManaged()).isFalse(); + assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue(); + + verify(projectAlmSettingDao).insertOrUpdate(any(), projectAlmSettingDtoCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq("generated_orga2/repo1")); + ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue(); + assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto); + } + + @Test + void createProjectAndBindToDevOpsPlatformFromScanner_whenRepoFoundOnGitHubAndVisibilitySynchronizationEnabled_successfullyCreatesProjectAndSetsVisibility() { + // given + mockPublicGithubRepository(); + + ComponentCreationData componentCreationData = mockProjectCreation("generated_orga2/repo1"); + ProjectAlmSettingDao projectAlmSettingDao = mock(); + when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); + when(gitHubSettings.isProvisioningEnabled()).thenReturn(true); + when(gitHubSettings.isProjectVisibilitySynchronizationActivated()).thenReturn(true); + + // when + ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), + SCANNER_API_DEVOPS_AUTO_CONFIG, false, null, null); + + // then + assertThat(actualComponentCreationData).isEqualTo(componentCreationData); + + ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); + assertThat(componentCreationParameters.newComponent().isPrivate()).isFalse(); + } + + @Test + void createProjectAndBindToDevOpsPlatformFromScanner_whenRepoFoundOnGitHubAndVisibilitySynchronizationDisabled_successfullyCreatesProjectAndMakesProjectPrivate() { + // given + mockGitHubRepository(); + + ComponentCreationData componentCreationData = mockProjectCreation("generated_orga2/repo1"); + ProjectAlmSettingDao projectAlmSettingDao = mock(); + when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); + when(gitHubSettings.isProvisioningEnabled()).thenReturn(true); + when(gitHubSettings.isProjectVisibilitySynchronizationActivated()).thenReturn(false); + + // when + ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), + SCANNER_API_DEVOPS_AUTO_CONFIG, false, null, null); + + // then + assertThat(actualComponentCreationData).isEqualTo(componentCreationData); + + ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); + assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue(); + } + + @Test + void createProjectAndBindToDevOpsPlatformFromApi_whenRepoFoundOnGitHub_successfullyCreatesProject() { + // given + String projectKey = "customProjectKey"; + mockGitHubRepository(); + + ComponentCreationData componentCreationData = mockProjectCreation(projectKey); + ProjectAlmSettingDao projectAlmSettingDao = mock(); + when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); + when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE); + + // when + ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), ALM_IMPORT_API, false, projectKey, + null); + + // then + assertThat(actualComponentCreationData).isEqualTo(componentCreationData); + + ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); + assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters, projectKey, ALM_IMPORT_API); + assertThat(componentCreationParameters.isManaged()).isFalse(); + assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue(); + + verify(projectAlmSettingDao).insertOrUpdate(any(), projectAlmSettingDtoCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq(projectKey)); + ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue(); + assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto); + } + + @Captor + private ArgumentCaptor> permissionChangesCaptor; + + @Test + void createProjectAndBindToDevOpsPlatformFromApi_whenRepoFoundOnGitHubAutoProvisioningOnAndRepoPrivate_successfullyCreatesProject() { + // given + String projectKey = "customProjectKey"; + mockGitHubRepository(); + + ComponentCreationData componentCreationData = mockProjectCreation(projectKey); + ProjectAlmSettingDao projectAlmSettingDao = mock(); + when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); + when(gitHubSettings.isProvisioningEnabled()).thenReturn(true); + + // when + ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), ALM_IMPORT_API, false, projectKey, + null); + + // then + assertThat(actualComponentCreationData).isEqualTo(componentCreationData); + + ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); + assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters, projectKey, ALM_IMPORT_API); + assertThat(componentCreationParameters.isManaged()).isTrue(); + assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue(); + + verifyScanPermissionWasAddedToUser(actualComponentCreationData); + verifyProjectSyncTaskWasCreated(actualComponentCreationData); + + verify(projectAlmSettingDao).insertOrUpdate(any(), projectAlmSettingDtoCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq(projectKey)); + ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue(); + assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto); + } + + private void verifyProjectSyncTaskWasCreated(ComponentCreationData componentCreationData) { + String projectUuid = requireNonNull(componentCreationData.projectDto()).getUuid(); + String mainBranchUuid = requireNonNull(componentCreationData.mainBranchDto()).getUuid(); + verify(managedProjectService).queuePermissionSyncTask(USER_UUID, mainBranchUuid, projectUuid); + } + + private void verifyScanPermissionWasAddedToUser(ComponentCreationData actualComponentCreationData) { + verify(permissionUpdater).apply(any(), permissionChangesCaptor.capture()); + UserPermissionChange permissionChange = permissionChangesCaptor.getValue().iterator().next(); + assertThat(permissionChange.getUserId().getUuid()).isEqualTo(userSession.getUuid()); + assertThat(permissionChange.getUserId().getLogin()).isEqualTo(userSession.getLogin()); + assertThat(permissionChange.getPermission()).isEqualTo(UserRole.SCAN); + assertThat(permissionChange.getProjectUuid()).isEqualTo(actualComponentCreationData.projectDto().getUuid()); + } + + private void mockPublicGithubRepository() { + GithubApplicationClient.Repository repository = mockGitHubRepository(); + when(repository.isPrivate()).thenReturn(false); + } + + private GithubApplicationClient.Repository mockGitHubRepository() { + GithubApplicationClient.Repository repository = mock(); + when(repository.getDefaultBranch()).thenReturn(MAIN_BRANCH_NAME); + when(repository.getName()).thenReturn(REPOSITORY_NAME); + when(repository.getFullName()).thenReturn(DEVOPS_PROJECT_DESCRIPTOR.projectIdentifier()); + lenient().when(repository.isPrivate()).thenReturn(true); + 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()); + return repository; + } + + private ComponentCreationData mockProjectCreation(String projectKey) { + ComponentCreationData componentCreationData = mock(); + ProjectDto projectDto = mockProjectDto(projectKey); + when(componentCreationData.projectDto()).thenReturn(projectDto); + BranchDto branchDto = mock(); + when(componentCreationData.mainBranchDto()).thenReturn(branchDto); + when(componentUpdater.createWithoutCommit(any(), componentCreationParametersCaptor.capture())).thenReturn(componentCreationData); + return componentCreationData; + } + + private static ProjectDto mockProjectDto(String projectKey) { + ProjectDto projectDto = mock(); + when(projectDto.getName()).thenReturn(REPOSITORY_NAME); + when(projectDto.getKey()).thenReturn(projectKey); + when(projectDto.getUuid()).thenReturn("project-uuid-1"); + return projectDto; + } + + private static void assertComponentCreationParametersContainsCorrectInformation(ComponentCreationParameters componentCreationParameters, String expectedKey, + CreationMethod expectedCreationMethod) { + assertThat(componentCreationParameters.creationMethod()).isEqualTo(expectedCreationMethod); + assertThat(componentCreationParameters.mainBranchName()).isEqualTo(MAIN_BRANCH_NAME); + assertThat(componentCreationParameters.userLogin()).isEqualTo(USER_LOGIN); + assertThat(componentCreationParameters.userUuid()).isEqualTo(USER_UUID); + + NewComponent newComponent = componentCreationParameters.newComponent(); + assertThat(newComponent.isProject()).isTrue(); + assertThat(newComponent.qualifier()).isEqualTo(Qualifiers.PROJECT); + assertThat(newComponent.key()).isEqualTo(expectedKey); + assertThat(newComponent.name()).isEqualTo(REPOSITORY_NAME); + } + + private static void assertAlmSettingsDtoContainsCorrectInformation(AlmSettingDto almSettingDto, ProjectDto projectDto, ProjectAlmSettingDto projectAlmSettingDto) { + assertThat(projectAlmSettingDto.getAlmRepo()).isEqualTo(DEVOPS_PROJECT_DESCRIPTOR.projectIdentifier()); + assertThat(projectAlmSettingDto.getAlmSlug()).isNull(); + assertThat(projectAlmSettingDto.getAlmSettingUuid()).isEqualTo(almSettingDto.getUuid()); + assertThat(projectAlmSettingDto.getProjectUuid()).isEqualTo(projectDto.getUuid()); + assertThat(projectAlmSettingDto.getMonorepo()).isFalse(); + assertThat(projectAlmSettingDto.getSummaryCommentEnabled()).isTrue(); + } +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorFactoryTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorFactoryTest.java new file mode 100644 index 00000000000..2abdd276753 --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorFactoryTest.java @@ -0,0 +1,66 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings.gitlab; + +import java.util.Map; +import org.assertj.core.api.AssertionsForClassTypes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GitlabProjectCreatorFactoryTest { + + @InjectMocks + private GitlabProjectCreatorFactory underTest; + + + @Test + void getDevOpsProjectCreator_withCharacteristics_returnsEmpty() { + assertThat(underTest.getDevOpsProjectCreator(mock(DbSession.class), Map.of())).isEmpty(); + } + + + @Test + void getDevOpsProjectCreator_whenDevOpsPlatformIsNotGitlab_returnsEmpty() { + AlmSettingDto almSetting = mock(); + when(almSetting.getAlm()).thenReturn(ALM.AZURE_DEVOPS); + AssertionsForClassTypes.assertThat(underTest.getDevOpsProjectCreator(almSetting, Mockito.mock(DevOpsProjectDescriptor.class))).isEmpty(); + } + + + @Test + void getDevOpsProjectCreator_whenDevOpsPlatformIsNotGitlab_returnsProjectCreator() { + AlmSettingDto almSetting = mock(); + when(almSetting.getAlm()).thenReturn(ALM.GITLAB); + assertThat(underTest.getDevOpsProjectCreator(almSetting, mock(DevOpsProjectDescriptor.class))).isNotEmpty(); + } + +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorTest.java new file mode 100644 index 00000000000..76e2d2da308 --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorTest.java @@ -0,0 +1,219 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.almsettings.gitlab; + +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Answers; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.sonar.alm.client.gitlab.GitLabBranch; +import org.sonar.alm.client.gitlab.GitlabApplicationClient; +import org.sonar.alm.client.gitlab.GitlabServerException; +import org.sonar.alm.client.gitlab.Project; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.pat.AlmPatDto; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; +import org.sonar.db.project.CreationMethod; +import org.sonar.db.project.ProjectDto; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.common.project.ProjectCreator; +import org.sonar.server.component.ComponentCreationData; +import org.sonar.server.user.UserSession; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GitlabProjectCreatorTest { + + private static final String PROJECT_UUID = "projectUuid"; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private DbClient dbClient; + + @Mock + private ProjectKeyGenerator projectKeyGenerator; + + @Mock + private ProjectCreator projectCreator; + + @Mock + private AlmSettingDto almSettingDto; + @Mock + private DevOpsProjectDescriptor devOpsProjectDescriptor; + @Mock + private GitlabApplicationClient gitlabApplicationClient; + @Mock + private UserSession userSession; + + @InjectMocks + private GitlabProjectCreator underTest; + + private static final String USER_LOGIN = "userLogin"; + private static final String USER_UUID = "userUuid"; + + private static final String GROUP_NAME = "group1"; + private static final String REPOSITORY_PATH_WITH_NAMESPACE = "pathWith/namespace"; + + private static final String GITLAB_PROJECT_NAME = "gitlabProjectName"; + + private static final String REPOSITORY_ID = "1234"; + + private static final String MAIN_BRANCH_NAME = "defaultBranch"; + + private static final String ALM_SETTING_KEY = "gitlab_config_1"; + private static final String ALM_SETTING_UUID = "almSettingUuid"; + + private static final String USER_PAT = "1234"; + + public static final String GITLAB_URL = "http://api.com"; + private static final DevOpsProjectDescriptor DEVOPS_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITLAB, GITLAB_URL, REPOSITORY_ID); + + @BeforeEach + void setup() { + lenient().when(userSession.getLogin()).thenReturn(USER_LOGIN); + lenient().when(userSession.getUuid()).thenReturn(USER_UUID); + + lenient().when(almSettingDto.getUrl()).thenReturn(GITLAB_URL); + lenient().when(almSettingDto.getKey()).thenReturn(ALM_SETTING_KEY); + lenient().when(almSettingDto.getUuid()).thenReturn(ALM_SETTING_UUID); + + lenient().when(devOpsProjectDescriptor.projectIdentifier()).thenReturn(REPOSITORY_ID); + lenient().when(devOpsProjectDescriptor.url()).thenReturn(GITLAB_URL); + lenient().when(devOpsProjectDescriptor.alm()).thenReturn(ALM.GITLAB); + } + + @Test + void isScanAllowedUsingPermissionsFromDevopsPlatform_shouldThrowUnsupportedOperationException() { + assertThatExceptionOfType(UnsupportedOperationException.class) + .isThrownBy(() -> underTest.isScanAllowedUsingPermissionsFromDevopsPlatform()) + .withMessage("Not Implemented"); + } + + @Test + void createProjectAndBindToDevOpsPlatform_whenUserHasNoPat_throws() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, false, null, null)) + .withMessage("personal access token for 'gitlab_config_1' is missing"); + } + + @Test + void createProjectAndBindToDevOpsPlatform_whenRepoNotFound_throws() { + mockPatForUser(); + when(gitlabApplicationClient.getProject(DEVOPS_PROJECT_DESCRIPTOR.url(), USER_PAT, Long.valueOf(REPOSITORY_ID))).thenThrow(new GitlabServerException(404, "Not found")); + assertThatExceptionOfType(IllegalStateException.class) + .isThrownBy(() -> underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, false, null, null)) + .withMessage("Failed to fetch GitLab project with ID '1234' from 'http://api.com'"); + + } + + @Test + void createProjectAndBindToDevOpsPlatform_whenRepoFoundOnGitlab_successfullyCreatesProject() { + mockPatForUser(); + mockGitlabProject(); + mockMainBranch(); + mockProjectCreation("projectKey", "projectName"); + + underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, true, "projectKey", "projectName"); + + ArgumentCaptor projectAlmSettingCaptor = ArgumentCaptor.forClass(ProjectAlmSettingDto.class); + + verify(dbClient.projectAlmSettingDao()).insertOrUpdate(any(), projectAlmSettingCaptor.capture(), eq(ALM_SETTING_KEY), eq("projectName"), eq("projectKey")); + + ProjectAlmSettingDto createdProjectAlmSettingDto = projectAlmSettingCaptor.getValue(); + + assertThat(createdProjectAlmSettingDto.getAlmSettingUuid()).isEqualTo(ALM_SETTING_UUID); + assertThat(createdProjectAlmSettingDto.getAlmRepo()).isEqualTo(REPOSITORY_ID); + assertThat(createdProjectAlmSettingDto.getProjectUuid()).isEqualTo(PROJECT_UUID); + assertThat(createdProjectAlmSettingDto.getMonorepo()).isTrue(); + + } + + @Test + void createProjectAndBindToDevOpsPlatform_whenNoKeyAndNameSpecified_generatesOneKeyAndUsersGitlabProjectName() { + mockPatForUser(); + mockGitlabProject(); + mockMainBranch(); + + String generatedProjectKey = "generatedProjectKey"; + when(projectKeyGenerator.generateUniqueProjectKey(REPOSITORY_PATH_WITH_NAMESPACE)).thenReturn(generatedProjectKey); + + mockProjectCreation(generatedProjectKey, GITLAB_PROJECT_NAME); + + underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, true, null, null); + + ArgumentCaptor projectAlmSettingCaptor = ArgumentCaptor.forClass(ProjectAlmSettingDto.class); + + verify(dbClient.projectAlmSettingDao()).insertOrUpdate(any(), projectAlmSettingCaptor.capture(), eq(ALM_SETTING_KEY), eq(GITLAB_PROJECT_NAME), eq(generatedProjectKey)); + + ProjectAlmSettingDto createdProjectAlmSettingDto = projectAlmSettingCaptor.getValue(); + + assertThat(createdProjectAlmSettingDto.getAlmSettingUuid()).isEqualTo(ALM_SETTING_UUID); + assertThat(createdProjectAlmSettingDto.getAlmRepo()).isEqualTo(REPOSITORY_ID); + assertThat(createdProjectAlmSettingDto.getProjectUuid()).isEqualTo(PROJECT_UUID); + assertThat(createdProjectAlmSettingDto.getMonorepo()).isTrue(); + } + + private void mockPatForUser() { + AlmPatDto almPatDto = mock(); + when(almPatDto.getPersonalAccessToken()).thenReturn(USER_PAT); + when(dbClient.almPatDao().selectByUserAndAlmSetting(any(), eq(USER_UUID), eq(almSettingDto))).thenReturn(Optional.of(almPatDto)); + } + + private void mockGitlabProject() { + Project project = mock(Project.class); + lenient().when(project.getPathWithNamespace()).thenReturn(REPOSITORY_PATH_WITH_NAMESPACE); + when(project.getName()).thenReturn(GITLAB_PROJECT_NAME); + when(gitlabApplicationClient.getProject(DEVOPS_PROJECT_DESCRIPTOR.url(), USER_PAT, Long.valueOf(REPOSITORY_ID))).thenReturn(project); + + } + + private void mockMainBranch() { + when(gitlabApplicationClient.getBranches(DEVOPS_PROJECT_DESCRIPTOR.url(), USER_PAT, Long.valueOf(REPOSITORY_ID))) + .thenReturn(List.of(new GitLabBranch("notMain", false), new GitLabBranch(MAIN_BRANCH_NAME, true))); + } + + private void mockProjectCreation(String projectKey, String projectName) { + ComponentCreationData componentCreationData = mock(); + ProjectDto projectDto = mock(); + when(componentCreationData.projectDto()).thenReturn(projectDto); + when(projectDto.getUuid()).thenReturn(PROJECT_UUID); + when(projectDto.getKey()).thenReturn(projectKey); + when(projectDto.getName()).thenReturn(projectName); + when(projectCreator.createProject(any(), eq(projectKey), eq(projectName), eq(MAIN_BRANCH_NAME), eq(CreationMethod.ALM_IMPORT_API))) + .thenReturn(componentCreationData); + } + +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/newcodeperiod/CaycUtilsTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/newcodeperiod/CaycUtilsTest.java new file mode 100644 index 00000000000..0afe761eeaa --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/newcodeperiod/CaycUtilsTest.java @@ -0,0 +1,85 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.newcodeperiod; + +import org.junit.Test; +import org.sonar.db.newcodeperiod.NewCodePeriodDto; +import org.sonar.db.newcodeperiod.NewCodePeriodType; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class CaycUtilsTest { + + @Test + public void reference_branch_is_compliant() { + var newCodePeriod = new NewCodePeriodDto() + .setType(NewCodePeriodType.REFERENCE_BRANCH) + .setValue("master"); + assertThat(CaycUtils.isNewCodePeriodCompliant(newCodePeriod.getType(), newCodePeriod.getValue())).isTrue(); + } + + @Test + public void previous_version_is_compliant() { + var newCodePeriod = new NewCodePeriodDto() + .setType(NewCodePeriodType.PREVIOUS_VERSION) + .setValue("1.0"); + assertThat(CaycUtils.isNewCodePeriodCompliant(newCodePeriod.getType(), newCodePeriod.getValue())).isTrue(); + } + + @Test + public void number_of_days_smaller_than_90_is_compliant() { + var newCodePeriod = new NewCodePeriodDto() + .setType(NewCodePeriodType.NUMBER_OF_DAYS) + .setValue("30"); + assertThat(CaycUtils.isNewCodePeriodCompliant(newCodePeriod.getType(), newCodePeriod.getValue())).isTrue(); + } + + @Test + public void number_of_days_smaller_than_1_is_not_compliant() { + var newCodePeriod = new NewCodePeriodDto() + .setType(NewCodePeriodType.NUMBER_OF_DAYS) + .setValue("0"); + assertThat(CaycUtils.isNewCodePeriodCompliant(newCodePeriod.getType(), newCodePeriod.getValue())).isFalse(); + } + + @Test + public void number_of_days_bigger_than_90_is_not_compliant() { + var newCodePeriod = new NewCodePeriodDto() + .setType(NewCodePeriodType.NUMBER_OF_DAYS) + .setValue("91"); + assertThat(CaycUtils.isNewCodePeriodCompliant(newCodePeriod.getType(), newCodePeriod.getValue())).isFalse(); + } + + @Test + public void specific_analysis_is_compliant() { + var newCodePeriod = new NewCodePeriodDto() + .setType(NewCodePeriodType.SPECIFIC_ANALYSIS) + .setValue("sdfsafsdf"); + assertThat(CaycUtils.isNewCodePeriodCompliant(newCodePeriod.getType(), newCodePeriod.getValue())).isTrue(); + } + + @Test + public void wrong_number_of_days_format_should_throw_exception() { + assertThatThrownBy(() -> CaycUtils.isNewCodePeriodCompliant(NewCodePeriodType.NUMBER_OF_DAYS, "abc")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Failed to parse number of days: abc"); + } +} \ No newline at end of file diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/newcodeperiod/NewCodeDefinitionResolverTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/newcodeperiod/NewCodeDefinitionResolverTest.java new file mode 100644 index 00000000000..6bba3336ad9 --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/newcodeperiod/NewCodeDefinitionResolverTest.java @@ -0,0 +1,147 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.common.newcodeperiod; + +import java.util.Optional; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.core.platform.PlatformEditionProvider; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.newcodeperiod.NewCodePeriodDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.sonar.db.newcodeperiod.NewCodePeriodType.NUMBER_OF_DAYS; +import static org.sonar.db.newcodeperiod.NewCodePeriodType.PREVIOUS_VERSION; +import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH; +import static org.sonar.db.newcodeperiod.NewCodePeriodType.SPECIFIC_ANALYSIS; + +public class NewCodeDefinitionResolverTest { + + private static final String MAIN_BRANCH_UUID = "main-branch-uuid"; + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + + private static final String DEFAULT_PROJECT_ID = "12345"; + + private static final String MAIN_BRANCH = "main"; + + private DbSession dbSession = db.getSession(); + private DbClient dbClient = db.getDbClient(); + private PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class); + private NewCodeDefinitionResolver newCodeDefinitionResolver = new NewCodeDefinitionResolver(db.getDbClient(), editionProvider); + + @Test + public void createNewCodeDefinition_throw_IAE_if_no_valid_type() { + assertThatThrownBy(() -> newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH_UUID, MAIN_BRANCH, "nonValid", null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Invalid type: nonValid"); + } + + @Test + public void createNewCodeDefinition_throw_IAE_if_type_is_not_allowed() { + assertThatThrownBy(() -> newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH_UUID, MAIN_BRANCH, SPECIFIC_ANALYSIS.name(), null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Invalid type 'SPECIFIC_ANALYSIS'. `newCodeDefinitionType` can only be set with types: [PREVIOUS_VERSION, NUMBER_OF_DAYS, REFERENCE_BRANCH]"); + } + + @Test + public void createNewCodeDefinition_throw_IAE_if_no_value_for_days() { + assertThatThrownBy(() -> newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH_UUID, MAIN_BRANCH, NUMBER_OF_DAYS.name(), null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("New code definition type 'NUMBER_OF_DAYS' requires a newCodeDefinitionValue"); + } + + @Test + public void createNewCodeDefinition_throw_IAE_if_days_is_invalid() { + assertThatThrownBy(() -> newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH_UUID, MAIN_BRANCH, NUMBER_OF_DAYS.name(), "unknown")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Failed to parse number of days: unknown"); + } + + @Test + public void createNewCodeDefinition_throw_IAE_if_value_is_set_for_reference_branch() { + assertThatThrownBy(() -> newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH_UUID, MAIN_BRANCH, REFERENCE_BRANCH.name(), "feature/zw")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Unexpected value for newCodeDefinitionType 'REFERENCE_BRANCH'"); + } + + @Test + public void createNewCodeDefinition_throw_IAE_if_previous_version_type_and_value_provided() { + assertThatThrownBy(() -> newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH_UUID, MAIN_BRANCH, PREVIOUS_VERSION.name(), "10.2.3")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Unexpected value for newCodeDefinitionType 'PREVIOUS_VERSION'"); + } + + @Test + public void createNewCodeDefinition_persist_previous_version_type() { + newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH_UUID, MAIN_BRANCH, PREVIOUS_VERSION.name(), null); + + Optional newCodePeriodDto = dbClient.newCodePeriodDao().selectByProject(dbSession, DEFAULT_PROJECT_ID); + assertThat(newCodePeriodDto).map(NewCodePeriodDto::getType).hasValue(PREVIOUS_VERSION); + } + + @Test + public void createNewCodeDefinition_return_days_value_for_number_of_days_type() { + String numberOfDays = "30"; + + newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH_UUID, MAIN_BRANCH, NUMBER_OF_DAYS.name(), numberOfDays); + + Optional newCodePeriodDto = dbClient.newCodePeriodDao().selectByProject(dbSession, DEFAULT_PROJECT_ID); + + assertThat(newCodePeriodDto) + .isPresent() + .get() + .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue) + .containsExactly(NUMBER_OF_DAYS, numberOfDays); + } + + @Test + public void createNewCodeDefinition_return_branch_value_for_reference_branch_type() { + newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH_UUID, MAIN_BRANCH, REFERENCE_BRANCH.name(), null); + + Optional newCodePeriodDto = dbClient.newCodePeriodDao().selectByProject(dbSession, DEFAULT_PROJECT_ID); + + assertThat(newCodePeriodDto) + .isPresent() + .get() + .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue, NewCodePeriodDto::getBranchUuid, NewCodePeriodDto::getProjectUuid) + .containsExactly(REFERENCE_BRANCH, MAIN_BRANCH, null, DEFAULT_PROJECT_ID); + } + + @Test + public void checkNewCodeDefinitionParam_throw_IAE_if_newCodeDefinitionValue_is_provided_without_newCodeDefinitionType() { + assertThatThrownBy(() -> newCodeDefinitionResolver.checkNewCodeDefinitionParam(null, "anyvalue")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("New code definition type is required when new code definition value is provided"); + } + + @Test + public void checkNewCodeDefinitionParam_do_not_throw_when_both_value_and_type_are_provided() { + assertThatNoException() + .isThrownBy(() -> newCodeDefinitionResolver.checkNewCodeDefinitionParam("PREVIOUS_VERSION", "anyvalue")); + } + +} diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectActionIT.java index 40455a78208..1ffde3bd74e 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectActionIT.java @@ -41,8 +41,8 @@ import org.sonar.db.project.CreationMethod; import org.sonar.db.project.ProjectDto; import org.sonar.db.user.UserDto; import org.sonar.server.almintegration.ws.ImportHelper; -import org.sonar.server.almintegration.ws.ProjectKeyGenerator; -import org.sonar.server.component.ComponentUpdater; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.component.ComponentUpdater; import org.sonar.server.es.TestIndexers; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; @@ -50,10 +50,10 @@ import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.favorite.FavoriteUpdater; import org.sonar.server.l18n.I18nRule; -import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; +import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; import org.sonar.server.permission.PermissionService; -import org.sonar.server.permission.PermissionTemplateService; -import org.sonar.server.permission.PermissionUpdater; +import org.sonar.server.common.permission.PermissionTemplateService; +import org.sonar.server.common.permission.PermissionUpdater; import org.sonar.server.project.DefaultBranchNameResolver; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.project.Visibility; diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionIT.java index 1405595b259..8dab56edddb 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionIT.java @@ -41,8 +41,8 @@ import org.sonar.db.project.CreationMethod; import org.sonar.db.project.ProjectDto; import org.sonar.db.user.UserDto; import org.sonar.server.almintegration.ws.ImportHelper; -import org.sonar.server.almintegration.ws.ProjectKeyGenerator; -import org.sonar.server.component.ComponentUpdater; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.component.ComponentUpdater; import org.sonar.server.es.TestIndexers; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; @@ -50,10 +50,10 @@ import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.favorite.FavoriteUpdater; import org.sonar.server.l18n.I18nRule; -import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; +import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; import org.sonar.server.permission.PermissionService; -import org.sonar.server.permission.PermissionTemplateService; -import org.sonar.server.permission.PermissionUpdater; +import org.sonar.server.common.permission.PermissionTemplateService; +import org.sonar.server.common.permission.PermissionUpdater; import org.sonar.server.project.DefaultBranchNameResolver; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.project.Visibility; diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionIT.java index 1ddecc7a524..55cb770a561 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionIT.java @@ -47,8 +47,8 @@ import org.sonar.db.project.CreationMethod; import org.sonar.db.project.ProjectDto; import org.sonar.db.user.UserDto; import org.sonar.server.almintegration.ws.ImportHelper; -import org.sonar.server.almintegration.ws.ProjectKeyGenerator; -import org.sonar.server.component.ComponentUpdater; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.component.ComponentUpdater; import org.sonar.server.es.TestIndexers; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; @@ -56,10 +56,10 @@ import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.favorite.FavoriteUpdater; import org.sonar.server.l18n.I18nRule; -import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; +import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; import org.sonar.server.permission.PermissionService; -import org.sonar.server.permission.PermissionTemplateService; -import org.sonar.server.permission.PermissionUpdater; +import org.sonar.server.common.permission.PermissionTemplateService; +import org.sonar.server.common.permission.PermissionUpdater; import org.sonar.server.project.DefaultBranchNameResolver; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.project.Visibility; 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 fed6d8d4f80..0bd1deaa6a1 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 @@ -51,9 +51,9 @@ import org.sonar.db.project.CreationMethod; import org.sonar.db.project.ProjectDto; import org.sonar.db.user.UserDto; import org.sonar.server.almintegration.ws.ImportHelper; -import org.sonar.server.almintegration.ws.ProjectKeyGenerator; -import org.sonar.server.almsettings.ws.GithubProjectCreatorFactory; -import org.sonar.server.component.ComponentUpdater; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.almsettings.github.GithubProjectCreatorFactory; +import org.sonar.server.common.component.ComponentUpdater; import org.sonar.server.es.EsTester; import org.sonar.server.es.IndexersImpl; import org.sonar.server.es.TestIndexers; @@ -62,20 +62,20 @@ import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.favorite.FavoriteUpdater; import org.sonar.server.management.ManagedProjectService; -import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; -import org.sonar.server.permission.GroupPermissionChanger; +import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; +import org.sonar.server.common.permission.GroupPermissionChanger; import org.sonar.server.permission.PermissionService; import org.sonar.server.permission.PermissionServiceImpl; -import org.sonar.server.permission.PermissionTemplateService; -import org.sonar.server.permission.PermissionUpdater; -import org.sonar.server.permission.UserPermissionChange; -import org.sonar.server.permission.UserPermissionChanger; +import org.sonar.server.common.permission.PermissionTemplateService; +import org.sonar.server.common.permission.PermissionUpdater; +import org.sonar.server.common.permission.UserPermissionChange; +import org.sonar.server.common.permission.UserPermissionChanger; import org.sonar.server.permission.index.FooIndexDefinition; import org.sonar.server.permission.index.PermissionIndexer; import org.sonar.server.project.DefaultBranchNameResolver; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.project.Visibility; -import org.sonar.server.project.ws.ProjectCreator; +import org.sonar.server.common.project.ProjectCreator; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.TestRequest; import org.sonar.server.ws.WsActionTester; diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionIT.java index 9d0b82db325..a9914d3c810 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionIT.java @@ -41,20 +41,20 @@ import org.sonar.db.project.CreationMethod; import org.sonar.db.project.ProjectDto; import org.sonar.db.user.UserDto; import org.sonar.server.almintegration.ws.ImportHelper; -import org.sonar.server.almintegration.ws.ProjectKeyGenerator; -import org.sonar.server.almsettings.ws.gitlab.GitlabProjectCreatorFactory; -import org.sonar.server.component.ComponentUpdater; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.almsettings.gitlab.GitlabProjectCreatorFactory; +import org.sonar.server.common.component.ComponentUpdater; import org.sonar.server.es.TestIndexers; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.favorite.FavoriteUpdater; -import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; +import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; import org.sonar.server.permission.PermissionService; -import org.sonar.server.permission.PermissionTemplateService; -import org.sonar.server.permission.PermissionUpdater; +import org.sonar.server.common.permission.PermissionTemplateService; +import org.sonar.server.common.permission.PermissionUpdater; import org.sonar.server.project.DefaultBranchNameResolver; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.project.Visibility; -import org.sonar.server.project.ws.ProjectCreator; +import org.sonar.server.common.project.ProjectCreator; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.TestRequest; import org.sonar.server.ws.WsActionTester; diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ce/queue/BranchReportSubmitterIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ce/queue/BranchReportSubmitterIT.java index 623c04f0f81..a71aa16951f 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ce/queue/BranchReportSubmitterIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ce/queue/BranchReportSubmitterIT.java @@ -47,18 +47,18 @@ import org.sonar.db.component.ProjectData; import org.sonar.db.project.CreationMethod; import org.sonar.db.project.ProjectDto; import org.sonar.db.user.UserDto; -import org.sonar.server.almsettings.ws.DevOpsProjectCreatorFactory; -import org.sonar.server.almsettings.ws.GithubProjectCreatorFactory; +import org.sonar.server.common.almsettings.DevOpsProjectCreatorFactory; +import org.sonar.server.common.almsettings.github.GithubProjectCreatorFactory; import org.sonar.server.component.ComponentCreationData; -import org.sonar.server.component.ComponentCreationParameters; -import org.sonar.server.component.ComponentUpdater; +import org.sonar.server.common.component.ComponentCreationParameters; +import org.sonar.server.common.component.ComponentUpdater; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.favorite.FavoriteUpdater; import org.sonar.server.management.ManagedInstanceService; -import org.sonar.server.permission.PermissionTemplateService; +import org.sonar.server.common.permission.PermissionTemplateService; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.project.Visibility; -import org.sonar.server.project.ws.ProjectCreator; +import org.sonar.server.common.project.ProjectCreator; import org.sonar.server.tester.UserSessionRule; import static java.util.Collections.emptyMap; 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 2e8b1e1e134..ef5e940f567 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 @@ -48,15 +48,15 @@ import org.sonar.db.permission.GlobalPermission; import org.sonar.db.project.CreationMethod; import org.sonar.db.project.ProjectDto; import org.sonar.db.user.UserDto; -import org.sonar.server.almintegration.ws.ProjectKeyGenerator; -import org.sonar.server.almsettings.ws.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.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.common.almintegration.ProjectKeyGenerator; +import org.sonar.server.common.almsettings.DelegatingDevOpsProjectCreatorFactory; +import org.sonar.server.common.almsettings.DevOpsProjectCreator; +import org.sonar.server.common.almsettings.DevOpsProjectCreatorFactory; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.common.almsettings.github.GithubProjectCreationParameters; +import org.sonar.server.common.almsettings.github.GithubProjectCreator; +import org.sonar.server.common.almsettings.github.GithubProjectCreatorFactory; +import org.sonar.server.common.component.ComponentUpdater; import org.sonar.server.es.TestIndexers; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; @@ -65,13 +65,13 @@ import org.sonar.server.management.ManagedInstanceService; import org.sonar.server.management.ManagedProjectService; import org.sonar.server.permission.PermissionService; import org.sonar.server.permission.PermissionServiceImpl; -import org.sonar.server.permission.PermissionTemplateService; -import org.sonar.server.permission.PermissionUpdater; -import org.sonar.server.permission.UserPermissionChange; +import org.sonar.server.common.permission.PermissionTemplateService; +import org.sonar.server.common.permission.PermissionUpdater; +import org.sonar.server.common.permission.UserPermissionChange; import org.sonar.server.project.DefaultBranchNameResolver; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.project.Visibility; -import org.sonar.server.project.ws.ProjectCreator; +import org.sonar.server.common.project.ProjectCreator; import org.sonar.server.tester.UserSessionRule; import static java.lang.String.format; diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ComponentUpdaterIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ComponentUpdaterIT.java deleted file mode 100644 index 11077edd3e4..00000000000 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ComponentUpdaterIT.java +++ /dev/null @@ -1,544 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.component; - -import java.util.List; -import java.util.Optional; -import java.util.Set; -import org.apache.commons.lang3.StringUtils; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.resources.Qualifiers; -import org.sonar.api.resources.Scopes; -import org.sonar.api.utils.System2; -import org.sonar.api.web.UserRole; -import org.sonar.core.util.SequenceUuidFactory; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.audit.AuditPersister; -import org.sonar.db.component.BranchDto; -import org.sonar.db.component.BranchType; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.component.ResourceTypesRule; -import org.sonar.db.project.CreationMethod; -import org.sonar.db.project.ProjectDto; -import org.sonar.db.user.UserDto; -import org.sonar.server.es.EsTester; -import org.sonar.server.es.Indexers; -import org.sonar.server.es.IndexersImpl; -import org.sonar.server.es.TestIndexers; -import org.sonar.server.exceptions.BadRequestException; -import org.sonar.server.favorite.FavoriteUpdater; -import org.sonar.server.l18n.I18nRule; -import org.sonar.server.permission.GroupPermissionChanger; -import org.sonar.server.permission.PermissionService; -import org.sonar.server.permission.PermissionServiceImpl; -import org.sonar.server.permission.PermissionTemplateService; -import org.sonar.server.permission.PermissionUpdater; -import org.sonar.server.permission.UserPermissionChange; -import org.sonar.server.permission.UserPermissionChanger; -import org.sonar.server.permission.index.FooIndexDefinition; -import org.sonar.server.permission.index.PermissionIndexer; -import org.sonar.server.project.DefaultBranchNameResolver; - -import static java.util.stream.IntStream.rangeClosed; -import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.sonar.api.resources.Qualifiers.APP; -import static org.sonar.api.resources.Qualifiers.PROJECT; -import static org.sonar.api.resources.Qualifiers.VIEW; -import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME; - -public class ComponentUpdaterIT { - - private static final String DEFAULT_PROJECT_KEY = "project-key"; - private static final String DEFAULT_PROJECT_NAME = "project-name"; - private static final NewComponent DEFAULT_COMPONENT = NewComponent.newComponentBuilder() - .setKey(DEFAULT_PROJECT_KEY) - .setName(DEFAULT_PROJECT_NAME) - .build(); - private static final NewComponent PRIVATE_COMPONENT = NewComponent.newComponentBuilder() - .setKey(DEFAULT_PROJECT_KEY) - .setName(DEFAULT_PROJECT_NAME) - .setPrivate(true) - .build(); - private static final String DEFAULT_USER_UUID = "user-uuid"; - public static final String DEFAULT_USER_LOGIN = "user-login"; - - private final System2 system2 = System2.INSTANCE; - - private final AuditPersister auditPersister = mock(); - - @Rule - public final DbTester db = DbTester.create(system2, auditPersister); - @Rule - public final I18nRule i18n = new I18nRule().put("qualifier.TRK", "Project"); - - private final TestIndexers projectIndexers = new TestIndexers(); - private final PermissionTemplateService permissionTemplateService = mock(PermissionTemplateService.class); - private final DefaultBranchNameResolver defaultBranchNameResolver = mock(DefaultBranchNameResolver.class); - public EsTester es = EsTester.createCustom(new FooIndexDefinition()); - private final PermissionUpdater userPermissionUpdater = new PermissionUpdater( - new IndexersImpl(new PermissionIndexer(db.getDbClient(), es.client())), - Set.of(new UserPermissionChanger(db.getDbClient(), new SequenceUuidFactory()), - new GroupPermissionChanger(db.getDbClient(), new SequenceUuidFactory()))); - private final PermissionService permissionService = new PermissionServiceImpl(new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT)); - - private final ComponentUpdater underTest = new ComponentUpdater(db.getDbClient(), i18n, system2, - permissionTemplateService, - new FavoriteUpdater(db.getDbClient()), - projectIndexers, new SequenceUuidFactory(), defaultBranchNameResolver, userPermissionUpdater, permissionService); - - @Before - public void before() { - when(defaultBranchNameResolver.getEffectiveMainBranchName()).thenReturn(DEFAULT_MAIN_BRANCH_NAME); - } - - @Test - public void persist_and_index_when_creating_project() { - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(PRIVATE_COMPONENT) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - ComponentCreationData returned = underTest.create(db.getSession(), creationParameters); - - ComponentDto loaded = db.getDbClient().componentDao().selectOrFailByUuid(db.getSession(), returned.mainBranchComponent().uuid()); - assertThat(loaded.getKey()).isEqualTo(DEFAULT_PROJECT_KEY); - assertThat(loaded.name()).isEqualTo(DEFAULT_PROJECT_NAME); - assertThat(loaded.longName()).isEqualTo(DEFAULT_PROJECT_NAME); - assertThat(loaded.qualifier()).isEqualTo(Qualifiers.PROJECT); - assertThat(loaded.scope()).isEqualTo(Scopes.PROJECT); - assertThat(loaded.uuid()).isNotNull(); - assertThat(loaded.branchUuid()).isEqualTo(loaded.uuid()); - assertThat(loaded.isPrivate()).isEqualTo(PRIVATE_COMPONENT.isPrivate()); - assertThat(loaded.getCreatedAt()).isNotNull(); - assertThat(db.getDbClient().componentDao().selectByKey(db.getSession(), DEFAULT_PROJECT_KEY)).isPresent(); - - assertThat(projectIndexers.hasBeenCalledForEntity(returned.projectDto().getUuid(), Indexers.EntityEvent.CREATION)).isTrue(); - - Optional branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), returned.mainBranchComponent().uuid()); - assertThat(branch).isPresent(); - assertThat(branch.get().getKey()).isEqualTo(DEFAULT_MAIN_BRANCH_NAME); - assertThat(branch.get().getMergeBranchUuid()).isNull(); - assertThat(branch.get().getBranchType()).isEqualTo(BranchType.BRANCH); - assertThat(branch.get().getUuid()).isEqualTo(returned.mainBranchComponent().uuid()); - assertThat(branch.get().getProjectUuid()).isEqualTo(returned.projectDto().getUuid()); - } - - @Test - public void create_project_with_main_branch_global_property() { - when(defaultBranchNameResolver.getEffectiveMainBranchName()).thenReturn("main-branch-global"); - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(PRIVATE_COMPONENT) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - - ComponentDto returned = underTest.create(db.getSession(), creationParameters).mainBranchComponent(); - - Optional branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), returned.branchUuid()); - assertThat(branch).get().extracting(BranchDto::getBranchKey).isEqualTo("main-branch-global"); - } - - @Test - public void persist_private_flag_true_when_creating_project() { - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(PRIVATE_COMPONENT) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - ComponentDto returned = underTest.create(db.getSession(), creationParameters).mainBranchComponent(); - ComponentDto loaded = db.getDbClient().componentDao().selectOrFailByUuid(db.getSession(), returned.uuid()); - assertThat(loaded.isPrivate()).isEqualTo(PRIVATE_COMPONENT.isPrivate()); - } - - @Test - public void persist_private_flag_false_when_creating_project() { - NewComponent project = NewComponent.newComponentBuilder() - .setKey(DEFAULT_PROJECT_KEY) - .setName(DEFAULT_PROJECT_NAME) - .setPrivate(false) - .build(); - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(project) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - ComponentDto returned = underTest.create(db.getSession(), creationParameters).mainBranchComponent(); - ComponentDto loaded = db.getDbClient().componentDao().selectOrFailByUuid(db.getSession(), returned.uuid()); - assertThat(loaded.isPrivate()).isEqualTo(project.isPrivate()); - } - - @Test - public void create_view() { - NewComponent view = NewComponent.newComponentBuilder() - .setKey("view-key") - .setName("view-name") - .setQualifier(VIEW) - .build(); - - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(view) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - ComponentDto returned = underTest.create(db.getSession(), creationParameters).mainBranchComponent(); - - ComponentDto loaded = db.getDbClient().componentDao().selectOrFailByUuid(db.getSession(), returned.uuid()); - assertThat(loaded.getKey()).isEqualTo("view-key"); - assertThat(loaded.name()).isEqualTo("view-name"); - assertThat(loaded.qualifier()).isEqualTo("VW"); - assertThat(projectIndexers.hasBeenCalledForEntity(loaded.uuid(), Indexers.EntityEvent.CREATION)).isTrue(); - Optional branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), returned.uuid()); - assertThat(branch).isNotPresent(); - } - - @Test - public void create_application() { - NewComponent application = NewComponent.newComponentBuilder() - .setKey("app-key") - .setName("app-name") - .setQualifier(APP) - .build(); - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(application) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - ComponentCreationData returned = underTest.create(db.getSession(), creationParameters); - - ProjectDto loaded = db.getDbClient().projectDao().selectByUuid(db.getSession(), returned.projectDto().getUuid()).get(); - assertThat(loaded.getKey()).isEqualTo("app-key"); - assertThat(loaded.getName()).isEqualTo("app-name"); - assertThat(loaded.getQualifier()).isEqualTo("APP"); - assertThat(projectIndexers.hasBeenCalledForEntity(loaded.getUuid(), Indexers.EntityEvent.CREATION)).isTrue(); - Optional branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), returned.mainBranchComponent().uuid()); - assertThat(branch).isPresent(); - assertThat(branch.get().getKey()).isEqualTo(DEFAULT_MAIN_BRANCH_NAME); - assertThat(branch.get().getMergeBranchUuid()).isNull(); - assertThat(branch.get().getBranchType()).isEqualTo(BranchType.BRANCH); - assertThat(branch.get().getUuid()).isEqualTo(returned.mainBranchComponent().uuid()); - assertThat(branch.get().getProjectUuid()).isEqualTo(returned.projectDto().getUuid()); - } - - @Test - public void apply_default_permission_template() { - ComponentCreationParameters componentCreationParameters = ComponentCreationParameters.builder() - .newComponent(DEFAULT_COMPONENT) - .userLogin(DEFAULT_USER_LOGIN) - .userUuid(DEFAULT_USER_UUID) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - - ProjectDto dto = underTest.create(db.getSession(), componentCreationParameters).projectDto(); - - verify(permissionTemplateService).applyDefaultToNewComponent(db.getSession(), dto, DEFAULT_USER_UUID); - } - - @Test - public void add_project_to_user_favorites_if_project_creator_is_defined_in_permission_template() { - UserDto userDto = db.users().insertUser(); - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(DEFAULT_COMPONENT) - .userLogin(userDto.getLogin()) - .userUuid(userDto.getUuid()) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - - when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), any(ProjectDto.class))) - .thenReturn(true); - - ProjectDto dto = underTest.create(db.getSession(), creationParameters).projectDto(); - - assertThat(db.favorites().hasFavorite(dto, userDto.getUuid())).isTrue(); - } - - @Test - public void do_not_add_project_to_user_favorites_if_project_creator_is_defined_in_permission_template_and_already_100_favorites() { - UserDto user = db.users().insertUser(); - rangeClosed(1, 100).forEach(i -> db.favorites().add(db.components().insertPrivateProject().getProjectDto(), user.getUuid(), user.getLogin())); - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(DEFAULT_COMPONENT) - .userLogin(user.getLogin()) - .userUuid(user.getUuid()) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - - when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(eq(db.getSession()), any(ProjectDto.class))) - .thenReturn(true); - - ProjectDto dto = underTest.create(db.getSession(), creationParameters).projectDto(); - - assertThat(db.favorites().hasFavorite(dto, user.getUuid())).isFalse(); - } - - @Test - public void does_not_add_project_to_favorite_when_anonymously_created() { - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(DEFAULT_COMPONENT) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - ProjectDto projectDto = underTest.create(db.getSession(), creationParameters).projectDto(); - - assertThat(db.favorites().hasNoFavorite(projectDto)).isTrue(); - } - - @Test - public void fail_when_project_key_already_exists() { - ComponentDto existing = db.components().insertPrivateProject().getMainBranchComponent(); - DbSession session = db.getSession(); - - NewComponent project = NewComponent.newComponentBuilder() - .setKey(existing.getKey()) - .setName(DEFAULT_PROJECT_NAME) - .build(); - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(project) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - - assertThatThrownBy(() -> underTest.create(session, creationParameters)) - .isInstanceOf(BadRequestException.class) - .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s\"", existing.getKey(), existing.getKey()); - } - - @Test - public void fail_when_key_has_bad_format() { - DbSession session = db.getSession(); - NewComponent project = NewComponent.newComponentBuilder() - .setKey("1234") - .setName(DEFAULT_PROJECT_NAME) - .build(); - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(project) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - - assertThatThrownBy(() -> underTest.create(session, creationParameters)) - .isInstanceOf(BadRequestException.class) - .hasMessageContaining("Malformed key for Project: '1234'"); - } - - @Test - public void fail_when_key_contains_percent_character() { - DbSession session = db.getSession(); - NewComponent project = NewComponent.newComponentBuilder() - .setKey("roject%Key") - .setName(DEFAULT_PROJECT_NAME) - .build(); - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(project) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - - assertThatThrownBy(() -> underTest.create(session, creationParameters)) - .isInstanceOf(BadRequestException.class) - .hasMessageContaining("Malformed key for Project: 'roject%Key'"); - } - - @Test - public void create_shouldFail_whenCreatingProjectWithExistingKeyButDifferentCase() { - createComponent_shouldFail_whenCreatingComponentWithExistingKeyButDifferentCase(PROJECT); - } - - @Test - public void create_shouldFail_whenCreatingPortfolioWithExistingKeyButDifferentCase() { - createComponent_shouldFail_whenCreatingComponentWithExistingKeyButDifferentCase(VIEW); - } - - @Test - public void create_shouldFail_whenCreatingApplicationWithExistingKeyButDifferentCase() { - createComponent_shouldFail_whenCreatingComponentWithExistingKeyButDifferentCase(APP); - } - - private void createComponent_shouldFail_whenCreatingComponentWithExistingKeyButDifferentCase(String qualifier) { - String existingKey = randomAlphabetic(5).toUpperCase(); - db.components().insertPrivateProject(component -> component.setKey(existingKey)); - String newKey = existingKey.toLowerCase(); - - NewComponent project = NewComponent.newComponentBuilder() - .setKey(newKey) - .setName(DEFAULT_PROJECT_NAME) - .setQualifier(qualifier) - .build(); - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(project) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - - DbSession dbSession = db.getSession(); - assertThatThrownBy(() -> underTest.create(dbSession, creationParameters)) - .isInstanceOf(BadRequestException.class) - .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s\"", newKey, existingKey); - } - - @Test - public void createComponent_shouldFail_whenCreatingComponentWithMultipleExistingKeyButDifferentCase() { - String existingKey = randomAlphabetic(5).toUpperCase(); - String existingKeyLowerCase = existingKey.toLowerCase(); - db.components().insertPrivateProject(component -> component.setKey(existingKey)); - db.components().insertPrivateProject(component -> component.setKey(existingKeyLowerCase)); - String newKey = StringUtils.capitalize(existingKeyLowerCase); - - NewComponent project = NewComponent.newComponentBuilder() - .setKey(newKey) - .setName(DEFAULT_PROJECT_NAME) - .build(); - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(project) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - - DbSession dbSession = db.getSession(); - assertThatThrownBy(() -> underTest.create(dbSession, creationParameters)) - .isInstanceOf(BadRequestException.class) - .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s, %s\"", newKey, existingKey, existingKeyLowerCase); - } - - @Test - public void createComponent_shouldFail_whenCreatingComponentWithMultipleExistingPortfolioKeysButDifferentCase() { - String existingKey = randomAlphabetic(5).toUpperCase(); - String existingKeyLowerCase = existingKey.toLowerCase(); - db.components().insertPrivatePortfolio(portfolio -> portfolio.setKey(existingKey)); - db.components().insertPrivatePortfolio(portfolio -> portfolio.setKey(existingKeyLowerCase)); - String newKey = StringUtils.capitalize(existingKeyLowerCase); - - NewComponent project = NewComponent.newComponentBuilder() - .setKey(newKey) - .setName(DEFAULT_PROJECT_NAME) - .build(); - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(project) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - - DbSession dbSession = db.getSession(); - assertThatThrownBy(() -> underTest.create(dbSession, creationParameters)) - .isInstanceOf(BadRequestException.class) - .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s, %s\"", newKey, existingKey, existingKeyLowerCase); - } - - @Test - public void create_createsComponentWithMasterBranchName() { - String componentNameAndKey = "createApplicationOrPortfolio"; - NewComponent app = NewComponent.newComponentBuilder() - .setKey(componentNameAndKey) - .setName(componentNameAndKey) - .setQualifier("APP") - .build(); - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(app) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - - ComponentDto appDto = underTest.create(db.getSession(), creationParameters).mainBranchComponent(); - - Optional branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), appDto.branchUuid()); - assertThat(branch).isPresent(); - assertThat(branch.get().getBranchKey()).isEqualTo(DEFAULT_MAIN_BRANCH_NAME); - } - - @Test - public void createWithoutCommit_whenProjectIsManaged_doesntApplyPermissionTemplate() { - UserDto userDto = db.users().insertUser(); - ComponentCreationParameters componentCreationParameters = ComponentCreationParameters.builder() - .newComponent(DEFAULT_COMPONENT) - .userLogin(userDto.getLogin()) - .userUuid(userDto.getUuid()) - .mainBranchName(null) - .isManaged(true) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - underTest.createWithoutCommit(db.getSession(), componentCreationParameters); - - verify(permissionTemplateService, never()).applyDefaultToNewComponent(any(), any(), any()); - } - - @Test - public void createWithoutCommit_whenInsertingPortfolio_shouldOnlyAddOneEntryToAuditLogs() { - String portfolioKey = "portfolio"; - NewComponent portfolio = NewComponent.newComponentBuilder() - .setKey(portfolioKey) - .setName(portfolioKey) - .setQualifier(VIEW) - .build(); - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(portfolio) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - - underTest.createWithoutCommit(db.getSession(), creationParameters); - db.commit(); - - verify(auditPersister, times(1)).addComponent(argThat(d -> d.equals(db.getSession())), - argThat(newValue -> newValue.getComponentKey().equals(portfolioKey))); - } - - @Test - public void createWithoutCommit_whenProjectIsManagedAndPrivate_applyPublicPermissionsToCreator() { - UserDto userDto = db.users().insertUser(); - NewComponent newComponent = NewComponent.newComponentBuilder() - .setKey(DEFAULT_PROJECT_KEY) - .setName(DEFAULT_PROJECT_NAME) - .setPrivate(true) - .build(); - - DbSession session = db.getSession(); - - ComponentCreationParameters componentCreationParameters = ComponentCreationParameters.builder() - .newComponent(PRIVATE_COMPONENT) - .userLogin(userDto.getLogin()) - .userUuid(userDto.getUuid()) - .mainBranchName(null) - .isManaged(true) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - ComponentCreationData componentCreationData = underTest.createWithoutCommit(session, componentCreationParameters); - - List permissions = db.getDbClient().userPermissionDao().selectEntityPermissionsOfUser(session, userDto.getUuid(), componentCreationData.projectDto().getUuid()); - assertThat(permissions) - .containsExactlyInAnyOrder(UserRole.USER, UserRole.CODEVIEWER); - } - - @Test - public void create_whenCreationMethodIsLocalApi_persistsIt() { - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(DEFAULT_COMPONENT) - .creationMethod(CreationMethod.LOCAL_API) - .build(); - ProjectDto projectDto = underTest.create(db.getSession(), creationParameters).projectDto(); - assertThat(projectDto.getCreationMethod()).isEqualTo(CreationMethod.LOCAL_API); - } - - @Test - public void create_whenCreationMethodIsAlmImportBrowser_persistsIt() { - ComponentCreationParameters creationParameters = ComponentCreationParameters.builder() - .newComponent(DEFAULT_COMPONENT) - .creationMethod(CreationMethod.ALM_IMPORT_BROWSER) - .build(); - ProjectDto projectDto = underTest.create(db.getSession(), creationParameters).projectDto(); - assertThat(projectDto.getCreationMethod()).isEqualTo(CreationMethod.ALM_IMPORT_BROWSER); - } -} diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/newcodeperiod/CaycUtilsTest.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/newcodeperiod/CaycUtilsTest.java deleted file mode 100644 index c69f1dbe818..00000000000 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/newcodeperiod/CaycUtilsTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.newcodeperiod; - -import org.junit.Test; -import org.sonar.db.newcodeperiod.NewCodePeriodDto; -import org.sonar.db.newcodeperiod.NewCodePeriodType; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -public class CaycUtilsTest { - - @Test - public void reference_branch_is_compliant() { - var newCodePeriod = new NewCodePeriodDto() - .setType(NewCodePeriodType.REFERENCE_BRANCH) - .setValue("master"); - assertThat(CaycUtils.isNewCodePeriodCompliant(newCodePeriod.getType(), newCodePeriod.getValue())).isTrue(); - } - - @Test - public void previous_version_is_compliant() { - var newCodePeriod = new NewCodePeriodDto() - .setType(NewCodePeriodType.PREVIOUS_VERSION) - .setValue("1.0"); - assertThat(CaycUtils.isNewCodePeriodCompliant(newCodePeriod.getType(), newCodePeriod.getValue())).isTrue(); - } - - @Test - public void number_of_days_smaller_than_90_is_compliant() { - var newCodePeriod = new NewCodePeriodDto() - .setType(NewCodePeriodType.NUMBER_OF_DAYS) - .setValue("30"); - assertThat(CaycUtils.isNewCodePeriodCompliant(newCodePeriod.getType(), newCodePeriod.getValue())).isTrue(); - } - - @Test - public void number_of_days_smaller_than_1_is_not_compliant() { - var newCodePeriod = new NewCodePeriodDto() - .setType(NewCodePeriodType.NUMBER_OF_DAYS) - .setValue("0"); - assertThat(CaycUtils.isNewCodePeriodCompliant(newCodePeriod.getType(), newCodePeriod.getValue())).isFalse(); - } - - @Test - public void number_of_days_bigger_than_90_is_not_compliant() { - var newCodePeriod = new NewCodePeriodDto() - .setType(NewCodePeriodType.NUMBER_OF_DAYS) - .setValue("91"); - assertThat(CaycUtils.isNewCodePeriodCompliant(newCodePeriod.getType(), newCodePeriod.getValue())).isFalse(); - } - - @Test - public void specific_analysis_is_compliant() { - var newCodePeriod = new NewCodePeriodDto() - .setType(NewCodePeriodType.SPECIFIC_ANALYSIS) - .setValue("sdfsafsdf"); - assertThat(CaycUtils.isNewCodePeriodCompliant(newCodePeriod.getType(), newCodePeriod.getValue())).isTrue(); - } - - @Test - public void wrong_number_of_days_format_should_throw_exception() { - assertThatThrownBy(() -> CaycUtils.isNewCodePeriodCompliant(NewCodePeriodType.NUMBER_OF_DAYS, "abc")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Failed to parse number of days: abc"); - } -} \ No newline at end of file diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/newcodeperiod/NewCodeDefinitionResolverTest.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/newcodeperiod/NewCodeDefinitionResolverTest.java deleted file mode 100644 index 332918ffed6..00000000000 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/newcodeperiod/NewCodeDefinitionResolverTest.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.newcodeperiod; - -import java.util.Optional; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.utils.System2; -import org.sonar.core.platform.PlatformEditionProvider; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.newcodeperiod.NewCodePeriodDto; -import org.sonar.server.component.ComponentCreationData; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.sonar.db.newcodeperiod.NewCodePeriodType.NUMBER_OF_DAYS; -import static org.sonar.db.newcodeperiod.NewCodePeriodType.PREVIOUS_VERSION; -import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH; -import static org.sonar.db.newcodeperiod.NewCodePeriodType.SPECIFIC_ANALYSIS; - -public class NewCodeDefinitionResolverTest { - - private static final String MAIN_BRANCH_UUID = "main-branch-uuid"; - @Rule - public DbTester db = DbTester.create(System2.INSTANCE); - - private static final String DEFAULT_PROJECT_ID = "12345"; - - private static final String MAIN_BRANCH = "main"; - - private DbSession dbSession = db.getSession(); - private DbClient dbClient = db.getDbClient(); - private PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class); - private NewCodeDefinitionResolver newCodeDefinitionResolver = new NewCodeDefinitionResolver(db.getDbClient(), editionProvider); - - @Test - public void createNewCodeDefinition_throw_IAE_if_no_valid_type() { - assertThatThrownBy(() -> newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH_UUID, MAIN_BRANCH, "nonValid", null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Invalid type: nonValid"); - } - - @Test - public void createNewCodeDefinition_throw_IAE_if_type_is_not_allowed() { - assertThatThrownBy(() -> newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH_UUID, MAIN_BRANCH, SPECIFIC_ANALYSIS.name(), null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Invalid type 'SPECIFIC_ANALYSIS'. `newCodeDefinitionType` can only be set with types: [PREVIOUS_VERSION, NUMBER_OF_DAYS, REFERENCE_BRANCH]"); - } - - @Test - public void createNewCodeDefinition_throw_IAE_if_no_value_for_days() { - assertThatThrownBy(() -> newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH_UUID, MAIN_BRANCH, NUMBER_OF_DAYS.name(), null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("New code definition type 'NUMBER_OF_DAYS' requires a newCodeDefinitionValue"); - } - - @Test - public void createNewCodeDefinition_throw_IAE_if_days_is_invalid() { - assertThatThrownBy(() -> newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH_UUID, MAIN_BRANCH, NUMBER_OF_DAYS.name(), "unknown")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Failed to parse number of days: unknown"); - } - - @Test - public void createNewCodeDefinition_throw_IAE_if_value_is_set_for_reference_branch() { - assertThatThrownBy(() -> newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH_UUID, MAIN_BRANCH, REFERENCE_BRANCH.name(), "feature/zw")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Unexpected value for newCodeDefinitionType 'REFERENCE_BRANCH'"); - } - - @Test - public void createNewCodeDefinition_throw_IAE_if_previous_version_type_and_value_provided() { - assertThatThrownBy(() -> newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH_UUID, MAIN_BRANCH, PREVIOUS_VERSION.name(), "10.2.3")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Unexpected value for newCodeDefinitionType 'PREVIOUS_VERSION'"); - } - - @Test - public void createNewCodeDefinition_persist_previous_version_type() { - newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH_UUID, MAIN_BRANCH, PREVIOUS_VERSION.name(), null); - - Optional newCodePeriodDto = dbClient.newCodePeriodDao().selectByProject(dbSession, DEFAULT_PROJECT_ID); - assertThat(newCodePeriodDto).map(NewCodePeriodDto::getType).hasValue(PREVIOUS_VERSION); - } - - @Test - public void createNewCodeDefinition_return_days_value_for_number_of_days_type() { - String numberOfDays = "30"; - - newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH_UUID, MAIN_BRANCH, NUMBER_OF_DAYS.name(), numberOfDays); - - Optional newCodePeriodDto = dbClient.newCodePeriodDao().selectByProject(dbSession, DEFAULT_PROJECT_ID); - - assertThat(newCodePeriodDto) - .isPresent() - .get() - .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue) - .containsExactly(NUMBER_OF_DAYS, numberOfDays); - } - - @Test - public void createNewCodeDefinition_return_branch_value_for_reference_branch_type() { - newCodeDefinitionResolver.createNewCodeDefinition(dbSession, DEFAULT_PROJECT_ID, MAIN_BRANCH_UUID, MAIN_BRANCH, REFERENCE_BRANCH.name(), null); - - Optional newCodePeriodDto = dbClient.newCodePeriodDao().selectByProject(dbSession, DEFAULT_PROJECT_ID); - - assertThat(newCodePeriodDto) - .isPresent() - .get() - .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue, NewCodePeriodDto::getBranchUuid, NewCodePeriodDto::getProjectUuid) - .containsExactly(REFERENCE_BRANCH, MAIN_BRANCH, null, DEFAULT_PROJECT_ID); - } - - @Test - public void checkNewCodeDefinitionParam_throw_IAE_if_newCodeDefinitionValue_is_provided_without_newCodeDefinitionType() { - assertThatThrownBy(() -> newCodeDefinitionResolver.checkNewCodeDefinitionParam(null, "anyvalue")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("New code definition type is required when new code definition value is provided"); - } - - @Test - public void checkNewCodeDefinitionParam_do_not_throw_when_both_value_and_type_are_provided() { - assertThatNoException() - .isThrownBy(() -> newCodeDefinitionResolver.checkNewCodeDefinitionParam("PREVIOUS_VERSION", "anyvalue")); - } - -} diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/DefaultTemplatesResolverImplIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/DefaultTemplatesResolverImplIT.java deleted file mode 100644 index 337e3630ca7..00000000000 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/DefaultTemplatesResolverImplIT.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.permission; - -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.utils.System2; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.component.ResourceTypesRule; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.sonar.api.resources.Qualifiers.APP; -import static org.sonar.api.resources.Qualifiers.PROJECT; -import static org.sonar.api.resources.Qualifiers.VIEW; - -public class DefaultTemplatesResolverImplIT { - - @Rule - public DbTester db = DbTester.create(System2.INSTANCE); - - private ResourceTypesRule resourceTypesWithPortfoliosInstalled = new ResourceTypesRule().setRootQualifiers(PROJECT, APP, VIEW); - private ResourceTypesRule resourceTypesWithApplicationInstalled = new ResourceTypesRule().setRootQualifiers(PROJECT, APP); - private ResourceTypesRule resourceTypes = new ResourceTypesRule().setRootQualifiers(PROJECT); - - private DefaultTemplatesResolverImpl underTestWithPortfoliosInstalled = new DefaultTemplatesResolverImpl(db.getDbClient(), resourceTypesWithPortfoliosInstalled); - private DefaultTemplatesResolverImpl underTestWithApplicationInstalled = new DefaultTemplatesResolverImpl(db.getDbClient(), resourceTypesWithApplicationInstalled); - private DefaultTemplatesResolverImpl underTest = new DefaultTemplatesResolverImpl(db.getDbClient(), resourceTypes); - - @Test - public void get_default_templates_when_portfolio_not_installed() { - db.permissionTemplates().setDefaultTemplates("prj", null, null); - - assertThat(underTest.resolve(db.getSession()).getProject()).contains("prj"); - assertThat(underTest.resolve(db.getSession()).getApplication()).isEmpty(); - assertThat(underTest.resolve(db.getSession()).getPortfolio()).isEmpty(); - } - - @Test - public void get_default_templates_always_return_project_template_even_when_all_templates_are_defined_but_portfolio_not_installed() { - db.permissionTemplates().setDefaultTemplates("prj", "app", "port"); - - assertThat(underTest.resolve(db.getSession()).getProject()).contains("prj"); - assertThat(underTest.resolve(db.getSession()).getApplication()).isEmpty(); - assertThat(underTest.resolve(db.getSession()).getPortfolio()).isEmpty(); - } - - @Test - public void get_default_templates_always_return_project_template_when_only_project_template_and_portfolio_is_installed_() { - db.permissionTemplates().setDefaultTemplates("prj", null, null); - - assertThat(underTestWithPortfoliosInstalled.resolve(db.getSession()).getProject()).contains("prj"); - assertThat(underTestWithPortfoliosInstalled.resolve(db.getSession()).getApplication()).contains("prj"); - assertThat(underTestWithPortfoliosInstalled.resolve(db.getSession()).getPortfolio()).contains("prj"); - } - - @Test - public void get_default_templates_for_all_components_when_portfolio_is_installed() { - db.permissionTemplates().setDefaultTemplates("prj", "app", "port"); - - assertThat(underTestWithPortfoliosInstalled.resolve(db.getSession()).getProject()).contains("prj"); - assertThat(underTestWithPortfoliosInstalled.resolve(db.getSession()).getApplication()).contains("app"); - assertThat(underTestWithPortfoliosInstalled.resolve(db.getSession()).getPortfolio()).contains("port"); - } - - @Test - public void get_default_templates_always_return_project_template_when_only_project_template_and_application_is_installed_() { - db.permissionTemplates().setDefaultTemplates("prj", null, null); - - assertThat(underTestWithApplicationInstalled.resolve(db.getSession()).getProject()).contains("prj"); - assertThat(underTestWithApplicationInstalled.resolve(db.getSession()).getApplication()).contains("prj"); - assertThat(underTestWithApplicationInstalled.resolve(db.getSession()).getPortfolio()).isEmpty(); - } - - @Test - public void get_default_templates_for_all_components_when_application_is_installed() { - db.permissionTemplates().setDefaultTemplates("prj", "app", null); - - assertThat(underTestWithApplicationInstalled.resolve(db.getSession()).getProject()).contains("prj"); - assertThat(underTestWithApplicationInstalled.resolve(db.getSession()).getApplication()).contains("app"); - assertThat(underTestWithApplicationInstalled.resolve(db.getSession()).getPortfolio()).isEmpty(); - } - - @Test - public void fail_when_default_template_for_project_is_missing() { - DbSession session = db.getSession(); - assertThatThrownBy(() -> underTestWithPortfoliosInstalled.resolve(session)) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Default template for project is missing"); - } - -} diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/GroupPermissionChangerIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/GroupPermissionChangerIT.java deleted file mode 100644 index 10c8f1a57de..00000000000 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/GroupPermissionChangerIT.java +++ /dev/null @@ -1,404 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.permission; - -import java.util.Set; -import org.apache.commons.lang3.StringUtils; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.resources.Qualifiers; -import org.sonar.api.resources.ResourceTypes; -import org.sonar.api.utils.System2; -import org.sonar.api.web.UserRole; -import org.sonar.core.util.SequenceUuidFactory; -import org.sonar.core.util.Uuids; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.component.ResourceTypesRule; -import org.sonar.db.permission.GlobalPermission; -import org.sonar.db.permission.GroupPermissionDto; -import org.sonar.db.project.ProjectDto; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; -import org.sonar.server.common.permission.Operation; -import org.sonar.server.exceptions.BadRequestException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Assertions.fail; -import static org.sonar.db.permission.GlobalPermission.ADMINISTER; -import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_GATES; -import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES; -import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; - -public class GroupPermissionChangerIT { - - @Rule - public DbTester db = DbTester.create(System2.INSTANCE); - - private final ResourceTypes resourceTypes = new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT); - private final PermissionService permissionService = new PermissionServiceImpl(resourceTypes); - private final GroupPermissionChanger underTest = new GroupPermissionChanger(db.getDbClient(), new SequenceUuidFactory()); - private GroupDto group; - - private ProjectDto privateProject; - private ProjectDto publicProject; - - @Before - public void setUp() { - group = db.users().insertGroup("a-group"); - privateProject = db.components().insertPrivateProject().getProjectDto(); - publicProject = db.components().insertPublicProject().getProjectDto(); - } - - @Test - public void apply_adds_global_permission_to_group() { - apply(new GroupPermissionChange(Operation.ADD, ADMINISTER_QUALITY_PROFILES.getKey(), null, group, permissionService)); - - assertThat(db.users().selectGroupPermissions(group, null)).containsOnly(ADMINISTER_QUALITY_PROFILES.getKey()); - } - - @Test - public void apply_adds_global_permission_to_group_AnyOne() { - apply(new GroupPermissionChange(Operation.ADD, ADMINISTER_QUALITY_PROFILES.getKey(), null, null, permissionService)); - - assertThat(db.users().selectAnyonePermissions(null)).containsOnly(ADMINISTER_QUALITY_PROFILES.getKey()); - } - - @Test - public void apply_fails_with_BadRequestException_when_adding_any_permission_to_group_AnyOne_on_private_project() { - permissionService.getAllProjectPermissions() - .forEach(perm -> { - GroupPermissionChange change = new GroupPermissionChange(Operation.ADD, perm, privateProject, null, permissionService); - try { - apply(change); - fail("a BadRequestException should have been thrown"); - } catch (BadRequestException e) { - assertThat(e).hasMessage("No permission can be granted to Anyone on a private component"); - } - }); - } - - @Test - public void apply_has_no_effect_when_removing_any_permission_to_group_AnyOne_on_private_project() { - permissionService.getAllProjectPermissions() - .forEach(this::unsafeInsertProjectPermissionOnAnyone); - - permissionService.getAllProjectPermissions() - .forEach(perm -> { - apply(new GroupPermissionChange(Operation.REMOVE, perm, privateProject, null, permissionService)); - - assertThat(db.users().selectAnyonePermissions(privateProject.getUuid())).contains(perm); - }); - } - - @Test - public void apply_adds_permission_USER_to_group_on_private_project() { - applyAddsPermissionToGroupOnPrivateProject(UserRole.USER); - } - - @Test - public void apply_adds_permission_CODEVIEWER_to_group_on_private_project() { - applyAddsPermissionToGroupOnPrivateProject(UserRole.CODEVIEWER); - } - - @Test - public void apply_adds_permission_ADMIN_to_group_on_private_project() { - applyAddsPermissionToGroupOnPrivateProject(UserRole.ADMIN); - } - - @Test - public void apply_adds_permission_ISSUE_ADMIN_to_group_on_private_project() { - applyAddsPermissionToGroupOnPrivateProject(UserRole.ISSUE_ADMIN); - } - - @Test - public void apply_adds_permission_SCAN_EXECUTION_to_group_on_private_project() { - applyAddsPermissionToGroupOnPrivateProject(GlobalPermission.SCAN.getKey()); - } - - private void applyAddsPermissionToGroupOnPrivateProject(String permission) { - - apply(new GroupPermissionChange(Operation.ADD, permission, privateProject, group, permissionService)); - - assertThat(db.users().selectGroupPermissions(group, null)).isEmpty(); - assertThat(db.users().selectGroupPermissions(group, privateProject)).containsOnly(permission); - } - - @Test - public void apply_removes_permission_USER_from_group_on_private_project() { - applyRemovesPermissionFromGroupOnPrivateProject(UserRole.USER); - } - - @Test - public void apply_removes_permission_CODEVIEWER_from_group_on_private_project() { - applyRemovesPermissionFromGroupOnPrivateProject(UserRole.CODEVIEWER); - } - - @Test - public void apply_removes_permission_ADMIN_from_on_private_project() { - applyRemovesPermissionFromGroupOnPrivateProject(UserRole.ADMIN); - } - - @Test - public void apply_removes_permission_ISSUE_ADMIN_from_on_private_project() { - applyRemovesPermissionFromGroupOnPrivateProject(UserRole.ISSUE_ADMIN); - } - - @Test - public void apply_removes_permission_SCAN_EXECUTION_from_on_private_project() { - applyRemovesPermissionFromGroupOnPrivateProject(GlobalPermission.SCAN.getKey()); - } - - private void applyRemovesPermissionFromGroupOnPrivateProject(String permission) { - db.users().insertEntityPermissionOnGroup(group, permission, privateProject); - - apply(new GroupPermissionChange(Operation.ADD, permission, privateProject, group, permissionService), permission); - - assertThat(db.users().selectGroupPermissions(group, privateProject)).containsOnly(permission); - } - - @Test - public void apply_has_no_effect_when_adding_USER_permission_to_group_AnyOne_on_a_public_project() { - apply(new GroupPermissionChange(Operation.ADD, UserRole.USER, publicProject, null, permissionService)); - - assertThat(db.users().selectAnyonePermissions(publicProject.getUuid())).isEmpty(); - } - - @Test - public void apply_has_no_effect_when_adding_CODEVIEWER_permission_to_group_AnyOne_on_a_public_project() { - apply(new GroupPermissionChange(Operation.ADD, UserRole.CODEVIEWER, publicProject, null, permissionService)); - - assertThat(db.users().selectAnyonePermissions(publicProject.getUuid())).isEmpty(); - } - - @Test - public void apply_fails_with_BadRequestException_when_adding_permission_ADMIN_to_group_AnyOne_on_a_public_project() { - GroupPermissionChange change = new GroupPermissionChange(Operation.ADD, UserRole.ADMIN, publicProject, null, permissionService); - assertThatThrownBy(() -> apply(change)) - .isInstanceOf(BadRequestException.class) - .hasMessage("It is not possible to add the 'admin' permission to group 'Anyone'."); - } - - @Test - public void apply_adds_permission_ISSUE_ADMIN_to_group_AnyOne_on_a_public_project() { - apply(new GroupPermissionChange(Operation.ADD, UserRole.ISSUE_ADMIN, publicProject, null, permissionService)); - - assertThat(db.users().selectAnyonePermissions(publicProject.getUuid())).containsOnly(UserRole.ISSUE_ADMIN); - } - - @Test - public void apply_adds_permission_SCAN_EXECUTION_to_group_AnyOne_on_a_public_project() { - apply(new GroupPermissionChange(Operation.ADD, GlobalPermission.SCAN.getKey(), publicProject, null, permissionService)); - - assertThat(db.users().selectAnyonePermissions(publicProject.getUuid())).containsOnly(GlobalPermission.SCAN.getKey()); - } - - @Test - public void apply_fails_with_BadRequestException_when_removing_USER_permission_from_group_AnyOne_on_a_public_project() { - GroupPermissionChange change = new GroupPermissionChange(Operation.REMOVE, UserRole.USER, publicProject, null, permissionService); - assertThatThrownBy(() -> apply(change)) - .isInstanceOf(BadRequestException.class) - .hasMessage("Permission user can't be removed from a public component"); - } - - @Test - public void apply_fails_with_BadRequestException_when_removing_CODEVIEWER_permission_from_group_AnyOne_on_a_public_project() { - GroupPermissionChange change = new GroupPermissionChange(Operation.REMOVE, UserRole.CODEVIEWER, publicProject, null, permissionService); - assertThatThrownBy(() -> apply(change)) - .isInstanceOf(BadRequestException.class) - .hasMessage("Permission codeviewer can't be removed from a public component"); - } - - @Test - public void apply_removes_ADMIN_permission_from_group_AnyOne_on_a_public_project() { - applyRemovesPermissionFromGroupAnyOneOnAPublicProject(UserRole.ADMIN); - } - - @Test - public void apply_removes_ISSUE_ADMIN_permission_from_group_AnyOne_on_a_public_project() { - applyRemovesPermissionFromGroupAnyOneOnAPublicProject(UserRole.ISSUE_ADMIN); - } - - @Test - public void apply_removes_SCAN_EXECUTION_permission_from_group_AnyOne_on_a_public_project() { - applyRemovesPermissionFromGroupAnyOneOnAPublicProject(GlobalPermission.SCAN.getKey()); - } - - private void applyRemovesPermissionFromGroupAnyOneOnAPublicProject(String permission) { - db.users().insertEntityPermissionOnAnyone(permission, publicProject); - - apply(new GroupPermissionChange(Operation.REMOVE, permission, publicProject, null, permissionService), permission); - - assertThat(db.users().selectAnyonePermissions(publicProject.getUuid())).isEmpty(); - } - - @Test - public void apply_fails_with_BadRequestException_when_removing_USER_permission_from_a_group_on_a_public_project() { - GroupPermissionChange change = new GroupPermissionChange(Operation.REMOVE, UserRole.USER, publicProject, group, permissionService); - assertThatThrownBy(() -> apply(change)) - .isInstanceOf(BadRequestException.class) - .hasMessage("Permission user can't be removed from a public component"); - } - - @Test - public void apply_fails_with_BadRequestException_when_removing_CODEVIEWER_permission_from_a_group_on_a_public_project() { - GroupPermissionChange change = new GroupPermissionChange(Operation.REMOVE, UserRole.CODEVIEWER, publicProject, group, permissionService); - assertThatThrownBy(() -> apply(change)) - .isInstanceOf(BadRequestException.class) - .hasMessage("Permission codeviewer can't be removed from a public component"); - } - - @Test - public void add_permission_to_anyone() { - apply(new GroupPermissionChange(Operation.ADD, ADMINISTER_QUALITY_PROFILES.getKey(), null, null, permissionService)); - - assertThat(db.users().selectGroupPermissions(group, null)).isEmpty(); - assertThat(db.users().selectAnyonePermissions(null)).containsOnly(ADMINISTER_QUALITY_PROFILES.getKey()); - } - - @Test - public void do_nothing_when_adding_permission_that_already_exists() { - db.users().insertPermissionOnGroup(group, ADMINISTER_QUALITY_GATES); - - apply(new GroupPermissionChange(Operation.ADD, ADMINISTER_QUALITY_GATES.getKey(), null, group, permissionService), ADMINISTER_QUALITY_GATES.getKey()); - - assertThat(db.users().selectGroupPermissions(group, null)).containsExactly(ADMINISTER_QUALITY_GATES.getKey()); - } - - @Test - public void fail_to_add_global_permission_but_SCAN_and_ADMIN_on_private_project() { - permissionService.getGlobalPermissions().stream() - .map(GlobalPermission::getKey) - .filter(perm -> !UserRole.ADMIN.equals(perm) && !GlobalPermission.SCAN.getKey().equals(perm)) - .forEach(perm -> { - try { - new GroupPermissionChange(Operation.ADD, perm, privateProject, group, permissionService); - fail("a BadRequestException should have been thrown for permission " + perm); - } catch (BadRequestException e) { - assertThat(e).hasMessage("Invalid project permission '" + perm + - "'. Valid values are [" + StringUtils.join(permissionService.getAllProjectPermissions(), ", ") + "]"); - } - }); - } - - @Test - public void fail_to_add_global_permission_but_SCAN_and_ADMIN_on_public_project() { - permissionService.getGlobalPermissions().stream() - .map(GlobalPermission::getKey) - .filter(perm -> !UserRole.ADMIN.equals(perm) && !GlobalPermission.SCAN.getKey().equals(perm)) - .forEach(perm -> { - try { - new GroupPermissionChange(Operation.ADD, perm, publicProject, group, permissionService); - fail("a BadRequestException should have been thrown for permission " + perm); - } catch (BadRequestException e) { - assertThat(e).hasMessage("Invalid project permission '" + perm + - "'. Valid values are [" + StringUtils.join(permissionService.getAllProjectPermissions(), ", ") + "]"); - } - }); - } - - @Test - public void fail_to_add_project_permission_but_SCAN_and_ADMIN_on_global_group() { - permissionService.getAllProjectPermissions() - .stream() - .filter(perm -> !GlobalPermission.SCAN.getKey().equals(perm) && !GlobalPermission.ADMINISTER.getKey().equals(perm)) - .forEach(permission -> { - try { - new GroupPermissionChange(Operation.ADD, permission, null, group, permissionService); - fail("a BadRequestException should have been thrown for permission " + permission); - } catch (BadRequestException e) { - assertThat(e).hasMessage("Invalid global permission '" + permission + "'. Valid values are [admin, gateadmin, profileadmin, provisioning, scan]"); - } - }); - } - - @Test - public void remove_permission_from_group() { - db.users().insertPermissionOnGroup(group, ADMINISTER_QUALITY_GATES); - db.users().insertPermissionOnGroup(group, PROVISION_PROJECTS); - - apply(new GroupPermissionChange(Operation.REMOVE, ADMINISTER_QUALITY_GATES.getKey(), null, group, permissionService), ADMINISTER_QUALITY_GATES.getKey(), - PROVISION_PROJECTS.getKey()); - - assertThat(db.users().selectGroupPermissions(group, null)).containsOnly(PROVISION_PROJECTS.getKey()); - } - - @Test - public void remove_project_permission_from_group() { - db.users().insertPermissionOnGroup(group, ADMINISTER_QUALITY_GATES); - db.users().insertEntityPermissionOnGroup(group, UserRole.ISSUE_ADMIN, privateProject); - db.users().insertEntityPermissionOnGroup(group, UserRole.CODEVIEWER, privateProject); - - apply(new GroupPermissionChange(Operation.REMOVE, UserRole.ISSUE_ADMIN, privateProject, group, permissionService), UserRole.ISSUE_ADMIN, - UserRole.CODEVIEWER); - - assertThat(db.users().selectGroupPermissions(group, null)).containsOnly(ADMINISTER_QUALITY_GATES.getKey()); - assertThat(db.users().selectGroupPermissions(group, privateProject)).containsOnly(UserRole.CODEVIEWER); - } - - @Test - public void do_not_fail_if_removing_a_permission_that_does_not_exist() { - apply(new GroupPermissionChange(Operation.REMOVE, UserRole.ISSUE_ADMIN, privateProject, group, permissionService)); - - assertThat(db.users().selectGroupPermissions(group, null)).isEmpty(); - assertThat(db.users().selectGroupPermissions(group, privateProject)).isEmpty(); - } - - @Test - public void fail_to_remove_admin_permission_if_no_more_admins() { - db.users().insertPermissionOnGroup(group, ADMINISTER); - - GroupPermissionChange change = new GroupPermissionChange(Operation.REMOVE, ADMINISTER.getKey(), null, group, permissionService); - Set permission = Set.of("admin"); - DbSession session = db.getSession(); - assertThatThrownBy(() -> underTest.apply(session, permission, change)) - .isInstanceOf(BadRequestException.class) - .hasMessage("Last group with permission 'admin'. Permission cannot be removed."); - } - - @Test - public void remove_admin_group_if_still_other_admins() { - db.users().insertPermissionOnGroup(group, ADMINISTER); - UserDto admin = db.users().insertUser(); - db.users().insertGlobalPermissionOnUser(admin, ADMINISTER); - - apply(new GroupPermissionChange(Operation.REMOVE, ADMINISTER.getKey(), null, group, permissionService), ADMINISTER.getKey()); - - assertThat(db.users().selectGroupPermissions(group, null)).isEmpty(); - } - - private void apply(GroupPermissionChange change, String... existingPermissions) { - underTest.apply(db.getSession(), Set.of(existingPermissions), change); - db.commit(); - } - - private void unsafeInsertProjectPermissionOnAnyone(String perm) { - GroupPermissionDto dto = new GroupPermissionDto() - .setUuid(Uuids.createFast()) - .setGroupUuid(null) - .setRole(perm) - .setEntityUuid(privateProject.getUuid()) - .setEntityName(privateProject.getName()); - db.getDbClient().groupPermissionDao().insert(db.getSession(), dto, privateProject, null); - db.commit(); - } -} diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/PermissionTemplateServiceIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/PermissionTemplateServiceIT.java deleted file mode 100644 index e0b83440ce6..00000000000 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/PermissionTemplateServiceIT.java +++ /dev/null @@ -1,482 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.permission; - -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import javax.annotation.Nullable; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.web.UserRole; -import org.sonar.core.util.SequenceUuidFactory; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.component.ResourceTypesRule; -import org.sonar.db.permission.GlobalPermission; -import org.sonar.db.permission.template.PermissionTemplateDbTester; -import org.sonar.db.permission.template.PermissionTemplateDto; -import org.sonar.db.portfolio.PortfolioDto; -import org.sonar.db.project.ProjectDto; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; -import org.sonar.server.es.Indexers; -import org.sonar.server.es.TestIndexers; -import org.sonar.server.exceptions.TemplateMatchingKeyException; -import org.sonar.server.tester.UserSessionRule; - -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.sonar.api.resources.Qualifiers.APP; -import static org.sonar.api.resources.Qualifiers.PROJECT; -import static org.sonar.api.resources.Qualifiers.VIEW; - -public class PermissionTemplateServiceIT { - - @Rule - public DbTester dbTester = DbTester.create(); - - private final ResourceTypesRule resourceTypesRule = new ResourceTypesRule().setRootQualifiers(PROJECT, VIEW, APP); - private final DefaultTemplatesResolver defaultTemplatesResolver = new DefaultTemplatesResolverImpl(dbTester.getDbClient(), resourceTypesRule); - private final PermissionService permissionService = new PermissionServiceImpl(resourceTypesRule); - private final UserSessionRule userSession = UserSessionRule.standalone(); - private final PermissionTemplateDbTester templateDb = dbTester.permissionTemplates(); - private final DbSession session = dbTester.getSession(); - private final Indexers indexers = new TestIndexers(); - private final PermissionTemplateService underTest = new PermissionTemplateService(dbTester.getDbClient(), indexers, userSession, defaultTemplatesResolver, - new SequenceUuidFactory()); - - @Test - public void apply_does_not_insert_permission_to_group_AnyOne_when_applying_template_on_private_project() { - ProjectDto privateProject = dbTester.components().insertPrivateProject().getProjectDto(); - PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); - dbTester.permissionTemplates().addAnyoneToTemplate(permissionTemplate, "p1"); - - underTest.applyAndCommit(session, permissionTemplate, singletonList(privateProject)); - - assertThat(selectProjectPermissionsOfGroup(null, privateProject.getUuid())).isEmpty(); - } - - @Test - public void apply_default_does_not_insert_permission_to_group_AnyOne_when_applying_template_on_private_project() { - ProjectDto privateProject = dbTester.components().insertPrivateProject().getProjectDto(); - UserDto creator = dbTester.users().insertUser(); - PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); - dbTester.permissionTemplates().addAnyoneToTemplate(permissionTemplate, "p1"); - dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); - - underTest.applyDefaultToNewComponent(session, privateProject, creator.getUuid()); - - assertThat(selectProjectPermissionsOfGroup(null, privateProject.getUuid())).isEmpty(); - } - - @Test - public void apply_inserts_permissions_to_group_AnyOne_but_USER_and_CODEVIEWER_when_applying_template_on_public_project() { - ProjectDto publicProject = dbTester.components().insertPublicProject().getProjectDto(); - PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); - permissionService.getAllProjectPermissions() - .forEach(perm -> dbTester.permissionTemplates().addAnyoneToTemplate(permissionTemplate, perm)); - dbTester.permissionTemplates().addAnyoneToTemplate(permissionTemplate, "p1"); - - underTest.applyAndCommit(session, permissionTemplate, singletonList(publicProject)); - - assertThat(selectProjectPermissionsOfGroup(null, publicProject.getUuid())) - .containsOnly("p1", UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); - } - - @Test - public void applyDefault_inserts_permissions_to_group_AnyOne_but_USER_and_CODEVIEWER_when_applying_template_on_public_project() { - ProjectDto publicProject = dbTester.components().insertPublicProject().getProjectDto(); - PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); - permissionService.getAllProjectPermissions() - .forEach(perm -> dbTester.permissionTemplates().addAnyoneToTemplate(permissionTemplate, perm)); - dbTester.permissionTemplates().addAnyoneToTemplate(permissionTemplate, "p1"); - dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); - - underTest.applyDefaultToNewComponent(session, publicProject, null); - - assertThat(selectProjectPermissionsOfGroup(null, publicProject.getUuid())) - .containsOnly("p1", UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); - } - - @Test - public void apply_inserts_any_permissions_to_group_when_applying_template_on_private_project() { - ProjectDto privateProject = dbTester.components().insertPrivateProject().getProjectDto(); - GroupDto group = dbTester.users().insertGroup(); - PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); - permissionService.getAllProjectPermissions() - .forEach(perm -> dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, perm)); - dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, "p1"); - - underTest.applyAndCommit(session, permissionTemplate, singletonList(privateProject)); - - assertThat(selectProjectPermissionsOfGroup(group, privateProject.getUuid())) - .containsOnly("p1", UserRole.CODEVIEWER, UserRole.USER, UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); - } - - @Test - public void applyDefault_inserts_any_permissions_to_group_when_applying_template_on_private_project() { - GroupDto group = dbTester.users().insertGroup(); - ProjectDto privateProject = dbTester.components().insertPrivateProject().getProjectDto(); - PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); - permissionService.getAllProjectPermissions() - .forEach(perm -> dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, perm)); - dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, "p1"); - dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); - - underTest.applyDefaultToNewComponent(session, privateProject, null); - - assertThat(selectProjectPermissionsOfGroup(group, privateProject.getUuid())) - .containsOnly("p1", UserRole.CODEVIEWER, UserRole.USER, UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); - } - - @Test - public void apply_inserts_permissions_to_group_but_USER_and_CODEVIEWER_when_applying_template_on_public_project() { - PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); - ProjectDto publicProject = dbTester.components().insertPublicProject().getProjectDto(); - GroupDto group = dbTester.users().insertGroup(); - permissionService.getAllProjectPermissions() - .forEach(perm -> dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, perm)); - dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, "p1"); - - underTest.applyAndCommit(session, permissionTemplate, singletonList(publicProject)); - - assertThat(selectProjectPermissionsOfGroup(group, publicProject.getUuid())) - .containsOnly("p1", UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); - } - - @Test - public void applyDefault_inserts_permissions_to_group_but_USER_and_CODEVIEWER_when_applying_template_on_public_project() { - PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); - ProjectDto publicProject = dbTester.components().insertPublicProject().getProjectDto(); - GroupDto group = dbTester.users().insertGroup(); - permissionService.getAllProjectPermissions() - .forEach(perm -> dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, perm)); - dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, "p1"); - dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); - - underTest.applyDefaultToNewComponent(session, publicProject, null); - - assertThat(selectProjectPermissionsOfGroup(group, publicProject.getUuid())) - .containsOnly("p1", UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); - } - - @Test - public void apply_inserts_permissions_to_user_but_USER_and_CODEVIEWER_when_applying_template_on_public_project() { - PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); - ProjectDto publicProject = dbTester.components().insertPublicProject().getProjectDto(); - UserDto user = dbTester.users().insertUser(); - permissionService.getAllProjectPermissions() - .forEach(perm -> dbTester.permissionTemplates().addUserToTemplate(permissionTemplate, user, perm)); - dbTester.permissionTemplates().addUserToTemplate(permissionTemplate, user, "p1"); - - underTest.applyAndCommit(session, permissionTemplate, singletonList(publicProject)); - - assertThat(selectProjectPermissionsOfUser(user, publicProject.getUuid())) - .containsOnly("p1", UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); - } - - @Test - public void applyDefault_inserts_permissions_to_user_but_USER_and_CODEVIEWER_when_applying_template_on_public_project() { - PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); - ProjectDto publicProject = dbTester.components().insertPublicProject().getProjectDto(); - UserDto user = dbTester.users().insertUser(); - permissionService.getAllProjectPermissions() - .forEach(perm -> dbTester.permissionTemplates().addUserToTemplate(permissionTemplate, user, perm)); - dbTester.permissionTemplates().addUserToTemplate(permissionTemplate, user, "p1"); - dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); - - underTest.applyDefaultToNewComponent(session, publicProject, null); - - assertThat(selectProjectPermissionsOfUser(user, publicProject.getUuid())) - .containsOnly("p1", UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); - } - - @Test - public void apply_inserts_any_permissions_to_user_when_applying_template_on_private_project() { - PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); - ProjectDto privateProject = dbTester.components().insertPrivateProject().getProjectDto(); - UserDto user = dbTester.users().insertUser(); - permissionService.getAllProjectPermissions() - .forEach(perm -> dbTester.permissionTemplates().addUserToTemplate(permissionTemplate, user, perm)); - dbTester.permissionTemplates().addUserToTemplate(permissionTemplate, user, "p1"); - - underTest.applyAndCommit(session, permissionTemplate, singletonList(privateProject)); - - assertThat(selectProjectPermissionsOfUser(user, privateProject.getUuid())) - .containsOnly("p1", UserRole.CODEVIEWER, UserRole.USER, UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); - } - - @Test - public void applyDefault_inserts_any_permissions_to_user_when_applying_template_on_private_project() { - PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); - ProjectDto privateProject = dbTester.components().insertPrivateProject().getProjectDto(); - UserDto user = dbTester.users().insertUser(); - permissionService.getAllProjectPermissions() - .forEach(perm -> dbTester.permissionTemplates().addUserToTemplate(permissionTemplate, user, perm)); - dbTester.permissionTemplates().addUserToTemplate(permissionTemplate, user, "p1"); - dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); - - underTest.applyDefaultToNewComponent(session, privateProject, null); - - assertThat(selectProjectPermissionsOfUser(user, privateProject.getUuid())) - .containsOnly("p1", UserRole.CODEVIEWER, UserRole.USER, UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); - } - - @Test - public void applyDefault_inserts_permissions_to_ProjectCreator_but_USER_and_CODEVIEWER_when_applying_template_on_public_project() { - PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); - ProjectDto publicProject = dbTester.components().insertPublicProject().getProjectDto(); - UserDto user = dbTester.users().insertUser(); - permissionService.getAllProjectPermissions() - .forEach(perm -> dbTester.permissionTemplates().addProjectCreatorToTemplate(permissionTemplate, perm)); - dbTester.permissionTemplates().addProjectCreatorToTemplate(permissionTemplate, "p1"); - dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); - - underTest.applyDefaultToNewComponent(session, publicProject, user.getUuid()); - - assertThat(selectProjectPermissionsOfUser(user, publicProject.getUuid())) - .containsOnly("p1", UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); - } - - @Test - public void applyDefault_inserts_any_permissions_to_ProjectCreator_when_applying_template_on_private_project() { - PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); - ProjectDto privateProject = dbTester.components().insertPrivateProject().getProjectDto(); - UserDto user = dbTester.users().insertUser(); - permissionService.getAllProjectPermissions() - .forEach(perm -> dbTester.permissionTemplates().addProjectCreatorToTemplate(permissionTemplate, perm)); - dbTester.permissionTemplates().addProjectCreatorToTemplate(permissionTemplate, "p1"); - dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); - - underTest.applyDefaultToNewComponent(session, privateProject, user.getUuid()); - - assertThat(selectProjectPermissionsOfUser(user, privateProject.getUuid())) - .containsOnly("p1", UserRole.CODEVIEWER, UserRole.USER, UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, GlobalPermission.SCAN.getKey()); - } - - @Test - public void apply_template_on_view() { - PortfolioDto portfolio = dbTester.components().insertPrivatePortfolioDto(); - PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); - GroupDto group = dbTester.users().insertGroup(); - dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, GlobalPermission.ADMINISTER.getKey()); - dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, GlobalPermission.PROVISION_PROJECTS.getKey()); - dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); - - underTest.applyDefaultToNewComponent(session, portfolio, null); - - assertThat(selectProjectPermissionsOfGroup(group, portfolio.getUuid())) - .containsOnly(GlobalPermission.ADMINISTER.getKey(), GlobalPermission.PROVISION_PROJECTS.getKey()); - } - - @Test - public void apply_default_template_on_application() { - ProjectDto application = dbTester.components().insertPublicApplication().getProjectDto(); - PermissionTemplateDto projectPermissionTemplate = dbTester.permissionTemplates().insertTemplate(); - PermissionTemplateDto appPermissionTemplate = dbTester.permissionTemplates().insertTemplate(); - GroupDto group = dbTester.users().insertGroup(); - dbTester.permissionTemplates().addGroupToTemplate(appPermissionTemplate, group, GlobalPermission.ADMINISTER.getKey()); - dbTester.permissionTemplates().addGroupToTemplate(appPermissionTemplate, group, GlobalPermission.PROVISION_PROJECTS.getKey()); - dbTester.permissionTemplates().setDefaultTemplates(projectPermissionTemplate, appPermissionTemplate, null); - - underTest.applyDefaultToNewComponent(session, application, null); - - assertThat(selectProjectPermissionsOfGroup(group, application.getUuid())) - .containsOnly(GlobalPermission.ADMINISTER.getKey(), GlobalPermission.PROVISION_PROJECTS.getKey()); - } - - @Test - public void apply_default_template_on_portfolio() { - PortfolioDto portfolio = dbTester.components().insertPublicPortfolioDto(); - PermissionTemplateDto projectPermissionTemplate = dbTester.permissionTemplates().insertTemplate(); - PermissionTemplateDto portPermissionTemplate = dbTester.permissionTemplates().insertTemplate(); - GroupDto group = dbTester.users().insertGroup(); - dbTester.permissionTemplates().addGroupToTemplate(portPermissionTemplate, group, GlobalPermission.ADMINISTER.getKey()); - dbTester.permissionTemplates().addGroupToTemplate(portPermissionTemplate, group, GlobalPermission.PROVISION_PROJECTS.getKey()); - dbTester.permissionTemplates().setDefaultTemplates(projectPermissionTemplate, null, portPermissionTemplate); - - underTest.applyDefaultToNewComponent(session, portfolio, null); - - assertThat(selectProjectPermissionsOfGroup(group, portfolio.getUuid())) - .containsOnly(GlobalPermission.ADMINISTER.getKey(), GlobalPermission.PROVISION_PROJECTS.getKey()); - } - - @Test - public void apply_project_default_template_on_view_when_no_view_default_template() { - PortfolioDto portfolio = dbTester.components().insertPrivatePortfolioDto(); - PermissionTemplateDto projectPermissionTemplate = dbTester.permissionTemplates().insertTemplate(); - GroupDto group = dbTester.users().insertGroup(); - dbTester.permissionTemplates().addGroupToTemplate(projectPermissionTemplate, group, GlobalPermission.PROVISION_PROJECTS.getKey()); - dbTester.permissionTemplates().setDefaultTemplates(projectPermissionTemplate, null, null); - - underTest.applyDefaultToNewComponent(session, portfolio, null); - - assertThat(selectProjectPermissionsOfGroup(group, portfolio.getUuid())).containsOnly(GlobalPermission.PROVISION_PROJECTS.getKey()); - } - - @Test - public void apply_template_on_applications() { - ProjectDto application = dbTester.components().insertPublicApplication().getProjectDto(); - PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); - GroupDto group = dbTester.users().insertGroup(); - dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, GlobalPermission.ADMINISTER.getKey()); - dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, group, GlobalPermission.PROVISION_PROJECTS.getKey()); - dbTester.permissionTemplates().setDefaultTemplates(permissionTemplate, null, null); - - underTest.applyDefaultToNewComponent(session, application, null); - - assertThat(selectProjectPermissionsOfGroup(group, application.getUuid())) - .containsOnly(GlobalPermission.ADMINISTER.getKey(), GlobalPermission.PROVISION_PROJECTS.getKey()); - } - - @Test - public void apply_default_view_template_on_application() { - ProjectDto application = dbTester.components().insertPublicApplication().getProjectDto(); - PermissionTemplateDto projectPermissionTemplate = dbTester.permissionTemplates().insertTemplate(); - PermissionTemplateDto appPermissionTemplate = dbTester.permissionTemplates().insertTemplate(); - PermissionTemplateDto portPermissionTemplate = dbTester.permissionTemplates().insertTemplate(); - GroupDto group = dbTester.users().insertGroup(); - dbTester.permissionTemplates().addGroupToTemplate(appPermissionTemplate, group, GlobalPermission.ADMINISTER.getKey()); - dbTester.permissionTemplates().addGroupToTemplate(appPermissionTemplate, group, GlobalPermission.PROVISION_PROJECTS.getKey()); - dbTester.permissionTemplates().setDefaultTemplates(projectPermissionTemplate, appPermissionTemplate, portPermissionTemplate); - - underTest.applyDefaultToNewComponent(session, application, null); - - assertThat(selectProjectPermissionsOfGroup(group, application.getUuid())) - .containsOnly(GlobalPermission.ADMINISTER.getKey(), GlobalPermission.PROVISION_PROJECTS.getKey()); - } - - @Test - public void apply_project_default_template_on_application_when_no_application_default_template() { - ProjectDto application = dbTester.components().insertPublicApplication().getProjectDto(); - PermissionTemplateDto projectPermissionTemplate = dbTester.permissionTemplates().insertTemplate(); - GroupDto group = dbTester.users().insertGroup(); - dbTester.permissionTemplates().addGroupToTemplate(projectPermissionTemplate, group, GlobalPermission.PROVISION_PROJECTS.getKey()); - dbTester.permissionTemplates().setDefaultTemplates(projectPermissionTemplate, null, null); - - underTest.applyDefaultToNewComponent(session, application, null); - - assertThat(selectProjectPermissionsOfGroup(group, application.getUuid())).containsOnly(GlobalPermission.PROVISION_PROJECTS.getKey()); - } - - @Test - public void apply_permission_template() { - UserDto user = dbTester.users().insertUser(); - ProjectDto project = dbTester.components().insertPrivateProject().getProjectDto(); - GroupDto adminGroup = dbTester.users().insertGroup(); - GroupDto userGroup = dbTester.users().insertGroup(); - dbTester.users().insertPermissionOnGroup(adminGroup, GlobalPermission.ADMINISTER.getKey()); - dbTester.users().insertPermissionOnGroup(userGroup, UserRole.USER); - dbTester.users().insertGlobalPermissionOnUser(user, GlobalPermission.ADMINISTER); - PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(); - dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, adminGroup, GlobalPermission.ADMINISTER.getKey()); - dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, adminGroup, UserRole.ISSUE_ADMIN); - dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, userGroup, UserRole.USER); - dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, userGroup, UserRole.CODEVIEWER); - dbTester.permissionTemplates().addAnyoneToTemplate(permissionTemplate, UserRole.USER); - dbTester.permissionTemplates().addAnyoneToTemplate(permissionTemplate, UserRole.CODEVIEWER); - dbTester.permissionTemplates().addUserToTemplate(permissionTemplate, user, GlobalPermission.ADMINISTER.getKey()); - - assertThat(selectProjectPermissionsOfGroup(adminGroup, project.getUuid())).isEmpty(); - assertThat(selectProjectPermissionsOfGroup(userGroup, project.getUuid())).isEmpty(); - assertThat(selectProjectPermissionsOfGroup(null, project.getUuid())).isEmpty(); - assertThat(selectProjectPermissionsOfUser(user, project.getUuid())).isEmpty(); - - underTest.applyAndCommit(session, permissionTemplate, singletonList(project)); - - assertThat(selectProjectPermissionsOfGroup(adminGroup, project.getUuid())).containsOnly(GlobalPermission.ADMINISTER.getKey(), UserRole.ISSUE_ADMIN); - assertThat(selectProjectPermissionsOfGroup(userGroup, project.getUuid())).containsOnly(UserRole.USER, UserRole.CODEVIEWER); - assertThat(selectProjectPermissionsOfGroup(null, project.getUuid())).isEmpty(); - assertThat(selectProjectPermissionsOfUser(user, project.getUuid())).containsOnly(GlobalPermission.ADMINISTER.getKey()); - } - - private List selectProjectPermissionsOfGroup(@Nullable GroupDto groupDto, String projectUuid) { - return dbTester.getDbClient().groupPermissionDao().selectEntityPermissionsOfGroup(session, groupDto != null ? groupDto.getUuid() : null, projectUuid); - } - - private List selectProjectPermissionsOfUser(UserDto userDto, String projectUuid) { - return dbTester.getDbClient().userPermissionDao().selectEntityPermissionsOfUser(session, userDto.getUuid(), projectUuid); - } - - @Test - public void would_user_have_scan_permission_with_default_permission_template() { - GroupDto group = dbTester.users().insertGroup(); - UserDto user = dbTester.users().insertUser(); - dbTester.users().insertMember(group, user); - PermissionTemplateDto template = templateDb.insertTemplate(); - dbTester.permissionTemplates().setDefaultTemplates(template, null, null); - templateDb.addProjectCreatorToTemplate(template.getUuid(), GlobalPermission.SCAN.getKey(), template.getName()); - templateDb.addUserToTemplate(template.getUuid(), user.getUuid(), UserRole.USER, template.getName(), user.getLogin()); - templateDb.addGroupToTemplate(template.getUuid(), group.getUuid(), UserRole.CODEVIEWER, template.getName(), group.getName()); - templateDb.addGroupToTemplate(template.getUuid(), null, UserRole.ISSUE_ADMIN, template.getName(), null); - - // authenticated user - checkWouldUserHaveScanPermission(user.getUuid(), true); - - // anonymous user - checkWouldUserHaveScanPermission(null, false); - } - - @Test - public void would_user_have_scan_permission_with_unknown_default_permission_template() { - dbTester.permissionTemplates().setDefaultTemplates("UNKNOWN_TEMPLATE_UUID", null, null); - - checkWouldUserHaveScanPermission(null, false); - } - - @Test - public void would_user_have_scan_permission_with_empty_template() { - PermissionTemplateDto template = templateDb.insertTemplate(); - dbTester.permissionTemplates().setDefaultTemplates(template, null, null); - - checkWouldUserHaveScanPermission(null, false); - } - - @Test - public void apply_permission_template_with_key_pattern_collision() { - final String key = "hi-test"; - final String keyPattern = ".*-test"; - - Stream templates = Stream.of( - templateDb.insertTemplate(t -> t.setKeyPattern(keyPattern)), - templateDb.insertTemplate(t -> t.setKeyPattern(keyPattern)) - ); - - String templateNames = templates - .map(PermissionTemplateDto::getName) - .sorted(String.CASE_INSENSITIVE_ORDER) - .map(x -> String.format("\"%s\"", x)) - .collect(Collectors.joining(", ")); - - ProjectDto project = dbTester.components().insertPrivateProject(p -> p.setKey(key)).getProjectDto(); - - assertThatThrownBy(() -> underTest.applyDefaultToNewComponent(session, project, null)) - .isInstanceOf(TemplateMatchingKeyException.class) - .hasMessageContaining("The \"%s\" key matches multiple permission templates: %s.", key, templateNames); - } - - private void checkWouldUserHaveScanPermission(@Nullable String userUuid, boolean expectedResult) { - assertThat(underTest.wouldUserHaveScanPermissionWithDefaultTemplate(session, userUuid, "PROJECT_KEY")) - .isEqualTo(expectedResult); - } - -} diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/UserPermissionChangerIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/UserPermissionChangerIT.java deleted file mode 100644 index f98ff2ae425..00000000000 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/UserPermissionChangerIT.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.permission; - -import java.util.Set; -import org.apache.commons.lang3.StringUtils; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.resources.Qualifiers; -import org.sonar.api.resources.ResourceTypes; -import org.sonar.api.utils.System2; -import org.sonar.api.web.UserRole; -import org.sonar.core.util.SequenceUuidFactory; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.component.ResourceTypesRule; -import org.sonar.db.entity.EntityDto; -import org.sonar.db.permission.GlobalPermission; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; -import org.sonar.db.user.UserIdDto; -import org.sonar.server.exceptions.BadRequestException; - -import static java.util.stream.Collectors.toSet; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.sonar.server.common.permission.Operation.ADD; -import static org.sonar.server.common.permission.Operation.REMOVE; -import static org.sonar.server.permission.PermissionServiceImpl.ALL_PROJECT_PERMISSIONS; - -public class UserPermissionChangerIT { - @Rule - public DbTester db = DbTester.create(System2.INSTANCE); - - private final ResourceTypes resourceTypes = new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT); - private final PermissionService permissionService = new PermissionServiceImpl(resourceTypes); - private final UserPermissionChanger underTest = new UserPermissionChanger(db.getDbClient(), new SequenceUuidFactory()); - private UserDto user1; - private UserDto user2; - private EntityDto privateProject; - private EntityDto publicProject; - - @Before - public void setUp() { - user1 = db.users().insertUser(); - user2 = db.users().insertUser(); - privateProject = db.components().insertPrivateProject().getProjectDto(); - publicProject = db.components().insertPublicProject().getProjectDto(); - } - - @Test - public void apply_adds_any_global_permission_to_user() { - permissionService.getGlobalPermissions() - .forEach(perm -> { - UserPermissionChange change = new UserPermissionChange(ADD, perm.getKey(), null, UserIdDto.from(user1), permissionService); - - apply(change); - - assertThat(db.users().selectPermissionsOfUser(user1)).contains(perm); - }); - } - - @Test - public void apply_removes_any_global_permission_to_user() { - // give ADMIN perm to user2 so that user1 is not the only one with this permission and it can be removed from user1 - db.users().insertGlobalPermissionOnUser(user2, GlobalPermission.ADMINISTER); - permissionService.getGlobalPermissions() - .forEach(perm -> db.users().insertGlobalPermissionOnUser(user1, perm)); - assertThat(db.users().selectPermissionsOfUser(user1)) - .containsOnly(permissionService.getGlobalPermissions().toArray(new GlobalPermission[0])); - - permissionService.getGlobalPermissions() - .forEach(perm -> { - UserPermissionChange change = new UserPermissionChange(REMOVE, perm.getKey(), null, UserIdDto.from(user1), permissionService); - - apply(change, permissionService.getGlobalPermissions().stream().map(GlobalPermission::getKey).collect(toSet())); - - assertThat(db.users().selectPermissionsOfUser(user1)).doesNotContain(perm); - }); - } - - @Test - public void apply_has_no_effect_when_adding_permission_USER_on_a_public_project() { - UserPermissionChange change = new UserPermissionChange(ADD, UserRole.USER, publicProject, UserIdDto.from(user1), permissionService); - - apply(change); - - assertThat(db.users().selectEntityPermissionOfUser(user1, publicProject.getUuid())).doesNotContain(UserRole.USER); - } - - @Test - public void apply_has_no_effect_when_adding_permission_CODEVIEWER_on_a_public_project() { - UserPermissionChange change = new UserPermissionChange(ADD, UserRole.CODEVIEWER, publicProject, UserIdDto.from(user1), permissionService); - - apply(change); - - assertThat(db.users().selectEntityPermissionOfUser(user1, publicProject.getUuid())).doesNotContain(UserRole.CODEVIEWER); - } - - @Test - public void apply_adds_permission_ADMIN_on_a_public_project() { - applyAddsPermissionOnAPublicProject(UserRole.ADMIN); - } - - @Test - public void apply_adds_permission_ISSUE_ADMIN_on_a_public_project() { - applyAddsPermissionOnAPublicProject(UserRole.ISSUE_ADMIN); - } - - @Test - public void apply_adds_permission_SCAN_EXECUTION_on_a_public_project() { - applyAddsPermissionOnAPublicProject(GlobalPermission.SCAN.getKey()); - } - - private void applyAddsPermissionOnAPublicProject(String permission) { - UserPermissionChange change = new UserPermissionChange(ADD, permission, publicProject, UserIdDto.from(user1), permissionService); - - apply(change); - - assertThat(db.users().selectEntityPermissionOfUser(user1, publicProject.getUuid())).containsOnly(permission); - } - - @Test - public void apply_fails_with_BadRequestException_when_removing_permission_USER_from_a_public_project() { - UserPermissionChange change = new UserPermissionChange(REMOVE, UserRole.USER, publicProject, UserIdDto.from(user1), permissionService); - - assertThatThrownBy(() -> apply(change)) - .isInstanceOf(BadRequestException.class) - .hasMessage("Permission user can't be removed from a public component"); - } - - @Test - public void apply_fails_with_BadRequestException_when_removing_permission_CODEVIEWER_from_a_public_project() { - UserPermissionChange change = new UserPermissionChange(REMOVE, UserRole.CODEVIEWER, publicProject, UserIdDto.from(user1), permissionService); - - assertThatThrownBy(() -> apply(change)) - .isInstanceOf(BadRequestException.class) - .hasMessage("Permission codeviewer can't be removed from a public component"); - } - - @Test - public void apply_removes_permission_ADMIN_from_a_public_project() { - applyRemovesPermissionFromPublicProject(UserRole.ADMIN); - } - - @Test - public void apply_removes_permission_ISSUE_ADMIN_from_a_public_project() { - applyRemovesPermissionFromPublicProject(UserRole.ISSUE_ADMIN); - } - - @Test - public void apply_removes_permission_SCAN_EXECUTION_from_a_public_project() { - applyRemovesPermissionFromPublicProject(GlobalPermission.SCAN.getKey()); - } - - private void applyRemovesPermissionFromPublicProject(String permission) { - db.users().insertProjectPermissionOnUser(user1, permission, publicProject); - UserPermissionChange change = new UserPermissionChange(REMOVE, permission, publicProject, UserIdDto.from(user1), permissionService); - - apply(change, Set.of(permission)); - - assertThat(db.users().selectEntityPermissionOfUser(user1, publicProject.getUuid())).isEmpty(); - } - - @Test - public void apply_adds_any_permission_to_a_private_project() { - permissionService.getAllProjectPermissions() - .forEach(permission -> { - UserPermissionChange change = new UserPermissionChange(ADD, permission, privateProject, UserIdDto.from(user1), permissionService); - - apply(change); - - assertThat(db.users().selectEntityPermissionOfUser(user1, privateProject.getUuid())).contains(permission); - }); - } - - @Test - public void apply_removes_any_permission_from_a_private_project() { - permissionService.getAllProjectPermissions() - .forEach(permission -> db.users().insertProjectPermissionOnUser(user1, permission, privateProject)); - - permissionService.getAllProjectPermissions() - .forEach(permission -> { - UserPermissionChange change = new UserPermissionChange(REMOVE, permission, privateProject, UserIdDto.from(user1), permissionService); - - apply(change, ALL_PROJECT_PERMISSIONS); - - assertThat(db.users().selectEntityPermissionOfUser(user1, privateProject.getUuid())).doesNotContain(permission); - }); - } - - @Test - public void add_global_permission_to_user() { - UserPermissionChange change = new UserPermissionChange(ADD, GlobalPermission.SCAN.getKey(), null, UserIdDto.from(user1), permissionService); - - apply(change); - - assertThat(db.users().selectPermissionsOfUser(user1)).containsOnly(GlobalPermission.SCAN); - assertThat(db.users().selectEntityPermissionOfUser(user1, privateProject.getUuid())).isEmpty(); - assertThat(db.users().selectPermissionsOfUser(user2)).isEmpty(); - assertThat(db.users().selectEntityPermissionOfUser(user2, privateProject.getUuid())).isEmpty(); - } - - @Test - public void add_project_permission_to_user() { - UserPermissionChange change = new UserPermissionChange(ADD, UserRole.ISSUE_ADMIN, privateProject, UserIdDto.from(user1), permissionService); - apply(change); - - assertThat(db.users().selectPermissionsOfUser(user1)).isEmpty(); - assertThat(db.users().selectEntityPermissionOfUser(user1, privateProject.getUuid())).contains(UserRole.ISSUE_ADMIN); - assertThat(db.users().selectPermissionsOfUser(user2)).isEmpty(); - assertThat(db.users().selectEntityPermissionOfUser(user2, privateProject.getUuid())).isEmpty(); - } - - @Test - public void do_nothing_when_adding_global_permission_that_already_exists() { - db.users().insertGlobalPermissionOnUser(user1, GlobalPermission.ADMINISTER_QUALITY_GATES); - - UserPermissionChange change = new UserPermissionChange(ADD, GlobalPermission.ADMINISTER_QUALITY_GATES.getKey(), null, UserIdDto.from(user1), permissionService); - apply(change); - - assertThat(db.users().selectPermissionsOfUser(user1)).containsOnly(GlobalPermission.ADMINISTER_QUALITY_GATES); - } - - @Test - public void fail_to_add_global_permission_on_project() { - assertThatThrownBy(() -> { - UserPermissionChange change = new UserPermissionChange(ADD, GlobalPermission.ADMINISTER_QUALITY_GATES.getKey(), privateProject, UserIdDto.from(user1), permissionService); - apply(change); - }) - .isInstanceOf(BadRequestException.class) - .hasMessage("Invalid project permission 'gateadmin'. Valid values are [" + StringUtils.join(permissionService.getAllProjectPermissions(), ", ") + "]"); - } - - @Test - public void fail_to_add_project_permission() { - assertThatThrownBy(() -> { - UserPermissionChange change = new UserPermissionChange(ADD, UserRole.ISSUE_ADMIN, null, UserIdDto.from(user1), permissionService); - apply(change); - }) - .isInstanceOf(BadRequestException.class) - .hasMessage("Invalid global permission 'issueadmin'. Valid values are [admin, gateadmin, profileadmin, provisioning, scan]"); - } - - @Test - public void remove_global_permission_from_user() { - db.users().insertGlobalPermissionOnUser(user1, GlobalPermission.ADMINISTER_QUALITY_GATES); - db.users().insertGlobalPermissionOnUser(user1, GlobalPermission.SCAN); - db.users().insertGlobalPermissionOnUser(user2, GlobalPermission.ADMINISTER_QUALITY_GATES); - db.users().insertProjectPermissionOnUser(user1, UserRole.ISSUE_ADMIN, privateProject); - - UserPermissionChange change = new UserPermissionChange(REMOVE, GlobalPermission.ADMINISTER_QUALITY_GATES.getKey(), null, UserIdDto.from(user1), permissionService); - apply(change, Set.of(GlobalPermission.ADMINISTER_QUALITY_GATES.getKey(), GlobalPermission.SCAN.getKey(), UserRole.ISSUE_ADMIN)); - - assertThat(db.users().selectPermissionsOfUser(user1)).containsOnly(GlobalPermission.SCAN); - assertThat(db.users().selectPermissionsOfUser(user2)).containsOnly(GlobalPermission.ADMINISTER_QUALITY_GATES); - assertThat(db.users().selectEntityPermissionOfUser(user1, privateProject.getUuid())).containsOnly(UserRole.ISSUE_ADMIN); - } - - @Test - public void remove_project_permission_from_user() { - EntityDto project2 = db.components().insertPrivateProject().getProjectDto(); - db.users().insertGlobalPermissionOnUser(user1, GlobalPermission.ADMINISTER_QUALITY_GATES); - db.users().insertProjectPermissionOnUser(user1, UserRole.ISSUE_ADMIN, privateProject); - db.users().insertProjectPermissionOnUser(user1, UserRole.USER, privateProject); - db.users().insertProjectPermissionOnUser(user2, UserRole.ISSUE_ADMIN, privateProject); - db.users().insertProjectPermissionOnUser(user1, UserRole.ISSUE_ADMIN, project2); - - UserPermissionChange change = new UserPermissionChange(REMOVE, UserRole.ISSUE_ADMIN, privateProject, UserIdDto.from(user1), permissionService); - apply(change, Set.of(GlobalPermission.ADMINISTER_QUALITY_GATES.getKey(), UserRole.ISSUE_ADMIN, UserRole.USER)); - - assertThat(db.users().selectEntityPermissionOfUser(user1, privateProject.getUuid())).containsOnly(UserRole.USER); - assertThat(db.users().selectEntityPermissionOfUser(user2, privateProject.getUuid())).containsOnly(UserRole.ISSUE_ADMIN); - assertThat(db.users().selectEntityPermissionOfUser(user1, project2.getUuid())).containsOnly(UserRole.ISSUE_ADMIN); - } - - @Test - public void do_not_fail_if_removing_a_global_permission_that_does_not_exist() { - UserPermissionChange change = new UserPermissionChange(REMOVE, GlobalPermission.ADMINISTER_QUALITY_GATES.getKey(), null, UserIdDto.from(user1), permissionService); - apply(change); - - assertThat(db.users().selectPermissionsOfUser(user1)).isEmpty(); - } - - @Test - public void do_not_fail_if_removing_a_project_permission_that_does_not_exist() { - UserPermissionChange change = new UserPermissionChange(REMOVE, UserRole.ISSUE_ADMIN, privateProject, UserIdDto.from(user1), permissionService); - apply(change); - - assertThat(db.users().selectEntityPermissionOfUser(user1, privateProject.getUuid())).isEmpty(); - } - - @Test - public void fail_to_remove_admin_global_permission_if_no_more_admins() { - db.users().insertGlobalPermissionOnUser(user1, GlobalPermission.ADMINISTER); - - UserPermissionChange change = new UserPermissionChange(REMOVE, GlobalPermission.ADMINISTER.getKey(), null, UserIdDto.from(user1), permissionService); - DbSession session = db.getSession(); - Set permissions = Set.of(GlobalPermission.ADMINISTER.getKey()); - assertThatThrownBy(() -> underTest.apply(session, permissions, change)) - .isInstanceOf(BadRequestException.class) - .hasMessage("Last user with permission 'admin'. Permission cannot be removed."); - } - - @Test - public void remove_admin_user_if_still_other_admins() { - db.users().insertGlobalPermissionOnUser(user1, GlobalPermission.ADMINISTER); - GroupDto admins = db.users().insertGroup("admins"); - db.users().insertMember(admins, user2); - db.users().insertPermissionOnGroup(admins, GlobalPermission.ADMINISTER); - - UserPermissionChange change = new UserPermissionChange(REMOVE, GlobalPermission.ADMINISTER.getKey(), null, UserIdDto.from(user1), permissionService); - underTest.apply(db.getSession(), Set.of(GlobalPermission.ADMINISTER.getKey()), change); - - assertThat(db.users().selectPermissionsOfUser(user1)).isEmpty(); - } - - private void apply(UserPermissionChange change) { - underTest.apply(db.getSession(), Set.of(), change); - db.commit(); - } - private void apply(UserPermissionChange change, Set existingPermissions) { - underTest.apply(db.getSession(), existingPermissions, change); - db.commit(); - } -} diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/BasePermissionWsIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/BasePermissionWsIT.java index f40a9adfc2d..70687fc68b2 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/BasePermissionWsIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/BasePermissionWsIT.java @@ -32,9 +32,9 @@ import org.sonar.db.component.ResourceTypesRule; import org.sonar.db.permission.template.PermissionTemplateDto; import org.sonar.server.es.EsTester; import org.sonar.server.es.IndexersImpl; -import org.sonar.server.permission.GroupPermissionChanger; -import org.sonar.server.permission.PermissionUpdater; -import org.sonar.server.permission.UserPermissionChanger; +import org.sonar.server.common.permission.GroupPermissionChanger; +import org.sonar.server.common.permission.PermissionUpdater; +import org.sonar.server.common.permission.UserPermissionChanger; import org.sonar.server.permission.index.FooIndexDefinition; import org.sonar.server.permission.index.PermissionIndexer; import org.sonar.server.tester.UserSessionRule; diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/ApplyTemplateActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/ApplyTemplateActionIT.java index 80482347f5f..47f1ca2f1ca 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/ApplyTemplateActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/ApplyTemplateActionIT.java @@ -40,9 +40,9 @@ import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.common.management.ManagedInstanceChecker; import org.sonar.server.management.ManagedProjectService; -import org.sonar.server.permission.DefaultTemplatesResolver; -import org.sonar.server.permission.DefaultTemplatesResolverImpl; -import org.sonar.server.permission.PermissionTemplateService; +import org.sonar.server.common.permission.DefaultTemplatesResolver; +import org.sonar.server.common.permission.DefaultTemplatesResolverImpl; +import org.sonar.server.common.permission.PermissionTemplateService; import org.sonar.server.permission.ws.BasePermissionWsIT; import org.sonar.server.ws.TestRequest; diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/BulkApplyTemplateActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/BulkApplyTemplateActionIT.java index 52f93e53f5c..f66366b10f4 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/BulkApplyTemplateActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/BulkApplyTemplateActionIT.java @@ -45,9 +45,9 @@ import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.l18n.I18nRule; import org.sonar.server.management.ManagedProjectService; -import org.sonar.server.permission.DefaultTemplatesResolver; -import org.sonar.server.permission.DefaultTemplatesResolverImpl; -import org.sonar.server.permission.PermissionTemplateService; +import org.sonar.server.common.permission.DefaultTemplatesResolver; +import org.sonar.server.common.permission.DefaultTemplatesResolverImpl; +import org.sonar.server.common.permission.PermissionTemplateService; import org.sonar.server.permission.ws.BasePermissionWsIT; import static org.assertj.core.api.Assertions.assertThat; diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/DeleteTemplateActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/DeleteTemplateActionIT.java index 1bb8621fbc8..3b52322edec 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/DeleteTemplateActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/DeleteTemplateActionIT.java @@ -38,8 +38,8 @@ import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.UnauthorizedException; -import org.sonar.server.permission.DefaultTemplatesResolver; -import org.sonar.server.permission.DefaultTemplatesResolverImpl; +import org.sonar.server.common.permission.DefaultTemplatesResolver; +import org.sonar.server.common.permission.DefaultTemplatesResolverImpl; import org.sonar.server.permission.ws.PermissionWsSupport; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.usergroups.DefaultGroupFinder; diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/SearchTemplatesActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/SearchTemplatesActionIT.java index ff91930e3be..6322ebe7a84 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/SearchTemplatesActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/SearchTemplatesActionIT.java @@ -35,8 +35,8 @@ import org.sonar.db.user.GroupDto; import org.sonar.db.user.UserDto; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.l18n.I18nRule; -import org.sonar.server.permission.DefaultTemplatesResolver; -import org.sonar.server.permission.DefaultTemplatesResolverImpl; +import org.sonar.server.common.permission.DefaultTemplatesResolver; +import org.sonar.server.common.permission.DefaultTemplatesResolverImpl; import org.sonar.server.permission.PermissionService; import org.sonar.server.permission.PermissionServiceImpl; import org.sonar.server.permission.ws.BasePermissionWsIT; diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/project/ws/CreateActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/project/ws/CreateActionIT.java index 42863cddfc0..305dd991f97 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/project/ws/CreateActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/project/ws/CreateActionIT.java @@ -38,17 +38,17 @@ import org.sonar.db.newcodeperiod.NewCodePeriodDto; import org.sonar.db.project.CreationMethod; import org.sonar.db.project.ProjectDto; import org.sonar.db.user.UserDto; -import org.sonar.server.component.ComponentUpdater; +import org.sonar.server.common.component.ComponentUpdater; import org.sonar.server.es.Indexers; import org.sonar.server.es.TestIndexers; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.favorite.FavoriteUpdater; import org.sonar.server.l18n.I18nRule; -import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; +import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; import org.sonar.server.permission.PermissionService; -import org.sonar.server.permission.PermissionTemplateService; -import org.sonar.server.permission.PermissionUpdater; +import org.sonar.server.common.permission.PermissionTemplateService; +import org.sonar.server.common.permission.PermissionUpdater; import org.sonar.server.project.DefaultBranchNameResolver; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.project.Visibility; @@ -71,8 +71,8 @@ import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME; import static org.sonar.db.newcodeperiod.NewCodePeriodType.NUMBER_OF_DAYS; import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH; import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; import static org.sonar.server.project.Visibility.PRIVATE; import static org.sonar.test.JsonAssert.assertJson; import static org.sonarqube.ws.client.WsRequest.Method.POST; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/ProjectKeyGenerator.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/ProjectKeyGenerator.java deleted file mode 100644 index 4a84afdc9f4..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/ProjectKeyGenerator.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.almintegration.ws; - -import com.google.common.annotations.VisibleForTesting; -import java.util.List; -import org.apache.commons.lang3.StringUtils; -import org.sonar.core.util.UuidFactory; - -import static com.google.common.collect.Lists.asList; -import static org.sonar.core.component.ComponentKeys.sanitizeProjectKey; - -public class ProjectKeyGenerator { - - @VisibleForTesting - static final int MAX_PROJECT_KEY_SIZE = 250; - @VisibleForTesting - static final Character PROJECT_KEY_SEPARATOR = '_'; - - private final UuidFactory uuidFactory; - - public ProjectKeyGenerator(UuidFactory uuidFactory) { - this.uuidFactory = uuidFactory; - } - - public String generateUniqueProjectKey(String projectName, String... extraProjectKeyItems) { - String sqProjectKey = generateCompleteProjectKey(projectName, extraProjectKeyItems); - sqProjectKey = truncateProjectKeyIfNecessary(sqProjectKey); - return sanitizeProjectKey(sqProjectKey); - } - - private String generateCompleteProjectKey(String projectName, String[] extraProjectKeyItems) { - List projectKeyItems = asList(projectName, extraProjectKeyItems); - String projectKey = StringUtils.join(projectKeyItems, PROJECT_KEY_SEPARATOR); - String uuid = uuidFactory.create(); - return projectKey + PROJECT_KEY_SEPARATOR + uuid; - } - - private static String truncateProjectKeyIfNecessary(String sqProjectKey) { - if (sqProjectKey.length() > MAX_PROJECT_KEY_SIZE) { - return sqProjectKey.substring(sqProjectKey.length() - MAX_PROJECT_KEY_SIZE); - } - return sqProjectKey; - } - -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectAction.java index 88bfc22573c..93d5001ed57 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectAction.java @@ -37,12 +37,12 @@ import org.sonar.db.component.BranchDto; import org.sonar.db.project.ProjectDto; import org.sonar.server.almintegration.ws.AlmIntegrationsWsAction; import org.sonar.server.almintegration.ws.ImportHelper; -import org.sonar.server.almintegration.ws.ProjectKeyGenerator; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; import org.sonar.server.component.ComponentCreationData; -import org.sonar.server.component.ComponentCreationParameters; -import org.sonar.server.component.ComponentUpdater; -import org.sonar.server.component.NewComponent; -import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; +import org.sonar.server.common.component.ComponentCreationParameters; +import org.sonar.server.common.component.ComponentUpdater; +import org.sonar.server.common.component.NewComponent; +import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; import org.sonar.server.project.DefaultBranchNameResolver; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.user.UserSession; @@ -54,10 +54,10 @@ import static org.sonar.db.project.CreationMethod.Category.ALM_IMPORT; import static org.sonar.db.project.CreationMethod.getCreationMethod; import static org.sonar.server.almintegration.ws.ImportHelper.PARAM_ALM_SETTING; import static org.sonar.server.almintegration.ws.ImportHelper.toCreateResponse; -import static org.sonar.server.component.NewComponent.newComponentBuilder; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; +import static org.sonar.server.common.component.NewComponent.newComponentBuilder; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_TYPE; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_VALUE; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java index 3834697bd0b..0f77f2fe2b6 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java @@ -38,12 +38,12 @@ import org.sonar.db.component.BranchDto; import org.sonar.db.project.ProjectDto; import org.sonar.server.almintegration.ws.AlmIntegrationsWsAction; import org.sonar.server.almintegration.ws.ImportHelper; -import org.sonar.server.almintegration.ws.ProjectKeyGenerator; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; import org.sonar.server.component.ComponentCreationData; -import org.sonar.server.component.ComponentCreationParameters; -import org.sonar.server.component.ComponentUpdater; -import org.sonar.server.component.NewComponent; -import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; +import org.sonar.server.common.component.ComponentCreationParameters; +import org.sonar.server.common.component.ComponentUpdater; +import org.sonar.server.common.component.NewComponent; +import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; import org.sonar.server.project.DefaultBranchNameResolver; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.user.UserSession; @@ -55,10 +55,10 @@ import static org.sonar.db.project.CreationMethod.Category.ALM_IMPORT; import static org.sonar.db.project.CreationMethod.getCreationMethod; import static org.sonar.server.almintegration.ws.ImportHelper.PARAM_ALM_SETTING; import static org.sonar.server.almintegration.ws.ImportHelper.toCreateResponse; -import static org.sonar.server.component.NewComponent.newComponentBuilder; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; +import static org.sonar.server.common.component.NewComponent.newComponentBuilder; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_TYPE; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_VALUE; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectAction.java index 9d7e589022e..6fc2d1bcb65 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectAction.java @@ -40,12 +40,12 @@ import org.sonar.db.component.BranchDto; import org.sonar.db.project.ProjectDto; import org.sonar.server.almintegration.ws.AlmIntegrationsWsAction; import org.sonar.server.almintegration.ws.ImportHelper; -import org.sonar.server.almintegration.ws.ProjectKeyGenerator; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; import org.sonar.server.component.ComponentCreationData; -import org.sonar.server.component.ComponentCreationParameters; -import org.sonar.server.component.ComponentUpdater; -import org.sonar.server.component.NewComponent; -import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; +import org.sonar.server.common.component.ComponentCreationParameters; +import org.sonar.server.common.component.ComponentUpdater; +import org.sonar.server.common.component.NewComponent; +import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; import org.sonar.server.project.DefaultBranchNameResolver; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.user.UserSession; @@ -57,10 +57,10 @@ import static org.sonar.db.project.CreationMethod.Category.ALM_IMPORT; import static org.sonar.db.project.CreationMethod.getCreationMethod; import static org.sonar.server.almintegration.ws.ImportHelper.PARAM_ALM_SETTING; import static org.sonar.server.almintegration.ws.ImportHelper.toCreateResponse; -import static org.sonar.server.component.NewComponent.newComponentBuilder; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; +import static org.sonar.server.common.component.NewComponent.newComponentBuilder; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_TYPE; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_VALUE; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java index 73632870db6..130078e2328 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java @@ -34,13 +34,13 @@ import org.sonar.db.project.CreationMethod; import org.sonar.db.project.ProjectDto; import org.sonar.server.almintegration.ws.AlmIntegrationsWsAction; import org.sonar.server.almintegration.ws.ImportHelper; -import org.sonar.server.almsettings.ws.DevOpsProjectCreator; -import org.sonar.server.almsettings.ws.DevOpsProjectDescriptor; -import org.sonar.server.almsettings.ws.GithubProjectCreatorFactory; +import org.sonar.server.common.almsettings.DevOpsProjectCreator; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.common.almsettings.github.GithubProjectCreatorFactory; import org.sonar.server.component.ComponentCreationData; -import org.sonar.server.component.ComponentUpdater; +import org.sonar.server.common.component.ComponentUpdater; import org.sonar.server.exceptions.BadRequestException; -import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; +import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; import org.sonar.server.project.DefaultBranchNameResolver; import org.sonar.server.user.UserSession; import org.sonarqube.ws.Projects; @@ -50,9 +50,9 @@ import static org.sonar.db.project.CreationMethod.getCreationMethod; import static org.sonar.db.project.CreationMethod.Category.ALM_IMPORT; import static org.sonar.server.almintegration.ws.ImportHelper.PARAM_ALM_SETTING; import static org.sonar.server.almintegration.ws.ImportHelper.toCreateResponse; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_TYPE; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_VALUE; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java index 264596f06f2..dc5e0ca7e0a 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java @@ -33,13 +33,13 @@ import org.sonar.db.component.BranchDto; import org.sonar.db.project.ProjectDto; import org.sonar.server.almintegration.ws.AlmIntegrationsWsAction; import org.sonar.server.almintegration.ws.ImportHelper; -import org.sonar.server.almsettings.ws.DevOpsProjectCreator; -import org.sonar.server.almsettings.ws.DevOpsProjectDescriptor; -import org.sonar.server.almsettings.ws.gitlab.GitlabProjectCreatorFactory; +import org.sonar.server.common.almsettings.DevOpsProjectCreator; +import org.sonar.server.common.almsettings.DevOpsProjectDescriptor; +import org.sonar.server.common.almsettings.gitlab.GitlabProjectCreatorFactory; import org.sonar.server.component.ComponentCreationData; -import org.sonar.server.component.ComponentUpdater; +import org.sonar.server.common.component.ComponentUpdater; import org.sonar.server.exceptions.BadRequestException; -import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; +import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; import org.sonar.server.user.UserSession; import org.sonarqube.ws.Projects.CreateWsResponse; @@ -47,9 +47,9 @@ import static java.util.Objects.requireNonNull; import static org.sonar.db.project.CreationMethod.getCreationMethod; import static org.sonar.db.project.CreationMethod.Category.ALM_IMPORT; import static org.sonar.server.almintegration.ws.ImportHelper.PARAM_ALM_SETTING; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_TYPE; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_VALUE; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DelegatingDevOpsProjectCreatorFactory.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DelegatingDevOpsProjectCreatorFactory.java deleted file mode 100644 index 1adf4d6c5d8..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DelegatingDevOpsProjectCreatorFactory.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.almsettings.ws; - -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import javax.annotation.Priority; -import org.sonar.api.server.ServerSide; -import org.sonar.db.DbSession; -import org.sonar.db.alm.setting.AlmSettingDto; - -@ServerSide -@Priority(1) -public class DelegatingDevOpsProjectCreatorFactory implements DevOpsProjectCreatorFactory { - - private final Set delegates; - - public DelegatingDevOpsProjectCreatorFactory(Set delegates) { - this.delegates = delegates; - } - - @Override - public Optional getDevOpsProjectCreator(DbSession dbSession, Map characteristics) { - return delegates.stream() - .flatMap(delegate -> delegate.getDevOpsProjectCreator(dbSession, characteristics).stream()) - .findFirst(); - } - - @Override - public Optional getDevOpsProjectCreator(AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor) { - return delegates.stream() - .flatMap(delegate -> delegate.getDevOpsProjectCreator(almSettingDto, devOpsProjectDescriptor).stream()) - .findFirst(); - } - -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectCreator.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectCreator.java deleted file mode 100644 index 3c18b3acaf2..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectCreator.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.almsettings.ws; - -import javax.annotation.Nullable; -import org.sonar.db.DbSession; -import org.sonar.db.project.CreationMethod; -import org.sonar.server.component.ComponentCreationData; - -public interface DevOpsProjectCreator { - - boolean isScanAllowedUsingPermissionsFromDevopsPlatform(); - - ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, CreationMethod creationMethod, Boolean monorepo, @Nullable String projectKey, - @Nullable String projectName); - -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectCreatorFactory.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectCreatorFactory.java deleted file mode 100644 index f409d2d239b..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectCreatorFactory.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.almsettings.ws; - -import java.util.Map; -import java.util.Optional; -import org.sonar.db.DbSession; -import org.sonar.db.alm.setting.AlmSettingDto; - -public interface DevOpsProjectCreatorFactory { - - Optional getDevOpsProjectCreator(DbSession dbSession, Map characteristics); - - Optional getDevOpsProjectCreator(AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor); - -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectDescriptor.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectDescriptor.java deleted file mode 100644 index e7df4243e96..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectDescriptor.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.almsettings.ws; - -import org.sonar.db.alm.setting.ALM; - -public record DevOpsProjectDescriptor(ALM alm, String url, String projectIdentifier) { -} 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 deleted file mode 100644 index dd4d19100bf..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreationParameters.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.almsettings.ws; - -import javax.annotation.Nullable; -import org.sonar.auth.github.AppInstallationToken; -import org.sonar.auth.github.security.AccessToken; -import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.server.user.UserSession; - -public record GithubProjectCreationParameters(DevOpsProjectDescriptor devOpsProjectDescriptor, AlmSettingDto almSettingDto, 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 deleted file mode 100644 index 8b1ca0d25e1..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreator.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.almsettings.ws; - -import java.util.Optional; -import java.util.Set; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import org.sonar.auth.github.AppInstallationToken; -import org.sonar.auth.github.GitHubSettings; -import org.sonar.auth.github.client.GithubApplicationClient; -import org.sonar.alm.client.github.GithubPermissionConverter; -import org.sonar.auth.github.GsonRepositoryCollaborator; -import org.sonar.auth.github.GsonRepositoryTeam; -import org.sonar.auth.github.client.GithubApplicationClient.Repository; -import org.sonar.auth.github.security.AccessToken; -import org.sonar.api.web.UserRole; -import org.sonar.auth.github.GsonRepositoryPermissions; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.db.alm.setting.ProjectAlmSettingDto; -import org.sonar.db.component.BranchDto; -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.db.user.UserIdDto; -import org.sonar.server.almintegration.ws.ProjectKeyGenerator; -import org.sonar.server.common.permission.Operation; -import org.sonar.server.component.ComponentCreationData; -import org.sonar.server.management.ManagedProjectService; -import org.sonar.server.permission.PermissionService; -import org.sonar.server.permission.PermissionUpdater; -import org.sonar.server.permission.UserPermissionChange; -import org.sonar.server.project.ws.ProjectCreator; -import org.sonar.server.user.UserSession; - -import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.toSet; -import static org.sonar.api.utils.Preconditions.checkState; - -public class GithubProjectCreator implements DevOpsProjectCreator { - - private final DbClient dbClient; - private final GithubApplicationClient githubApplicationClient; - private final GithubPermissionConverter githubPermissionConverter; - private final ProjectKeyGenerator projectKeyGenerator; - private final PermissionUpdater permissionUpdater; - private final PermissionService permissionService; - private final ManagedProjectService managedProjectService; - private final ProjectCreator projectCreator; - private final GithubProjectCreationParameters githubProjectCreationParameters; - private final DevOpsProjectDescriptor devOpsProjectDescriptor; - private final UserSession userSession; - private final AlmSettingDto almSettingDto; - private final AccessToken devOpsAppInstallationToken; - private final GitHubSettings gitHubSettings; - - @CheckForNull - private final AppInstallationToken authAppInstallationToken; - - public GithubProjectCreator(DbClient dbClient, GithubApplicationClient githubApplicationClient, GithubPermissionConverter githubPermissionConverter, - ProjectKeyGenerator projectKeyGenerator, PermissionUpdater permissionUpdater, PermissionService permissionService, - ManagedProjectService managedProjectService, ProjectCreator projectCreator, GithubProjectCreationParameters githubProjectCreationParameters, GitHubSettings gitHubSettings) { - - this.dbClient = dbClient; - this.githubApplicationClient = githubApplicationClient; - this.githubPermissionConverter = githubPermissionConverter; - this.projectKeyGenerator = projectKeyGenerator; - this.permissionUpdater = permissionUpdater; - this.permissionService = permissionService; - this.managedProjectService = managedProjectService; - this.projectCreator = projectCreator; - this.githubProjectCreationParameters = githubProjectCreationParameters; - userSession = githubProjectCreationParameters.userSession(); - almSettingDto = githubProjectCreationParameters.almSettingDto(); - devOpsProjectDescriptor = githubProjectCreationParameters.devOpsProjectDescriptor(); - devOpsAppInstallationToken = githubProjectCreationParameters.devOpsAppInstallationToken(); - authAppInstallationToken = githubProjectCreationParameters.authAppInstallationToken(); - this.gitHubSettings = gitHubSettings; - } - - @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]; - - Set permissionsMappingDtos = dbClient.githubPermissionsMappingDao().findAll(dbClient.openSession(false)); - - boolean userHasDirectAccessToRepo = doesUserHaveScanPermission(organization, repository, permissionsMappingDtos); - if (userHasDirectAccessToRepo) { - return true; - } - return doesUserBelongToAGroupWithScanPermission(organization, repository, permissionsMappingDtos); - } - - private boolean doesUserHaveScanPermission(String organization, String repository, Set permissionsMappingDtos) { - Set repositoryCollaborators = githubApplicationClient.getRepositoryCollaborators(devOpsProjectDescriptor.url(), authAppInstallationToken, - organization, repository); - - String externalLogin = userSession.getExternalIdentity().map(UserSession.ExternalIdentity::login).orElse(null); - if (externalLogin == null) { - return false; - } - return repositoryCollaborators.stream() - .filter(gsonRepositoryCollaborator -> externalLogin.equals(gsonRepositoryCollaborator.name())) - .findAny() - .map(gsonRepositoryCollaborator -> hasScanPermission(permissionsMappingDtos, gsonRepositoryCollaborator.roleName(), gsonRepositoryCollaborator.permissions())) - .orElse(false); - } - - private boolean doesUserBelongToAGroupWithScanPermission(String organization, String repository, - Set permissionsMappingDtos) { - Set repositoryTeams = githubApplicationClient.getRepositoryTeams(devOpsProjectDescriptor.url(), authAppInstallationToken, organization, repository); - - Set groupsOfUser = findUserMembershipOnSonarQube(organization); - return repositoryTeams.stream() - .filter(team -> hasScanPermission(permissionsMappingDtos, team.permission(), team.permissions())) - .map(GsonRepositoryTeam::name) - .anyMatch(groupsOfUser::contains); - } - - private Set findUserMembershipOnSonarQube(String organization) { - return userSession.getGroups().stream() - .map(GroupDto::getName) - .filter(groupName -> groupName.contains("/")) - .map(name -> name.replaceFirst(organization + "/", "")) - .collect(toSet()); - } - - private boolean hasScanPermission(Set permissionsMappingDtos, String role, GsonRepositoryPermissions permissions) { - Set sonarqubePermissions = githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(permissionsMappingDtos, - role, permissions); - return sonarqubePermissions.contains(UserRole.SCAN); - } - - @Override - public ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, CreationMethod creationMethod, Boolean monorepo, @Nullable String projectKey, - @Nullable String projectName) { - String url = requireNonNull(almSettingDto.getUrl(), "DevOps Platform url cannot be null"); - 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()))); - - return createProjectAndBindToDevOpsPlatform(dbSession, monorepo, projectKey, projectName, almSettingDto, repository, creationMethod); - } - - private ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, Boolean monorepo, @Nullable String projectKey, @Nullable String projectName, - AlmSettingDto almSettingDto, - Repository repository, CreationMethod creationMethod) { - String key = Optional.ofNullable(projectKey).orElse(getUniqueProjectKey(repository)); - - boolean isManaged = gitHubSettings.isProvisioningEnabled(); - - ComponentCreationData componentCreationData = projectCreator.createProject(dbSession, key, Optional.ofNullable(projectName).orElse(repository.getName()), - repository.getDefaultBranch(), creationMethod, - shouldProjectBePrivate(repository), isManaged); - ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow(); - createProjectAlmSettingDto(dbSession, repository, projectDto, almSettingDto, monorepo); - addScanPermissionToCurrentUser(dbSession, projectDto); - - BranchDto mainBranchDto = Optional.ofNullable(componentCreationData.mainBranchDto()).orElseThrow(); - if (gitHubSettings.isProvisioningEnabled()) { - syncProjectPermissionsWithGithub(projectDto, mainBranchDto); - } - return componentCreationData; - } - - @CheckForNull - private Boolean shouldProjectBePrivate(Repository repository) { - if (gitHubSettings.isProvisioningEnabled() && gitHubSettings.isProjectVisibilitySynchronizationActivated()) { - return repository.isPrivate(); - } else if (gitHubSettings.isProvisioningEnabled()) { - return true; - } else { - return null; - } - } - - private void addScanPermissionToCurrentUser(DbSession dbSession, ProjectDto projectDto) { - UserIdDto userId = new UserIdDto(requireNonNull(userSession.getUuid()), requireNonNull(userSession.getLogin())); - UserPermissionChange scanPermission = new UserPermissionChange(Operation.ADD, UserRole.SCAN, projectDto, userId, permissionService); - permissionUpdater.apply(dbSession, Set.of(scanPermission)); - } - - private void syncProjectPermissionsWithGithub(ProjectDto projectDto, BranchDto mainBranchDto) { - String userUuid = requireNonNull(userSession.getUuid()); - managedProjectService.queuePermissionSyncTask(userUuid, mainBranchDto.getUuid(), projectDto.getUuid()); - } - - private String getUniqueProjectKey(Repository repository) { - return projectKeyGenerator.generateUniqueProjectKey(repository.getFullName()); - } - - private void createProjectAlmSettingDto(DbSession dbSession, Repository repo, ProjectDto projectDto, AlmSettingDto almSettingDto, Boolean monorepo) { - ProjectAlmSettingDto projectAlmSettingDto = new ProjectAlmSettingDto() - .setAlmSettingUuid(almSettingDto.getUuid()) - .setAlmRepo(repo.getFullName()) - .setAlmSlug(null) - .setProjectUuid(projectDto.getUuid()) - .setSummaryCommentEnabled(true) - .setMonorepo(monorepo); - dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, projectAlmSettingDto, almSettingDto.getKey(), projectDto.getName(), projectDto.getKey()); - } - -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactory.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactory.java deleted file mode 100644 index 935a8b1d85f..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactory.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.almsettings.ws; - -import java.util.Map; -import java.util.Optional; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.alm.client.github.GithubGlobalSettingsValidator; -import org.sonar.alm.client.github.GithubPermissionConverter; -import org.sonar.api.server.ServerSide; -import org.sonar.auth.github.AppInstallationToken; -import org.sonar.auth.github.GitHubSettings; -import org.sonar.auth.github.GithubAppConfiguration; -import org.sonar.auth.github.client.GithubApplicationClient; -import org.sonar.auth.github.security.AccessToken; -import org.sonar.auth.github.security.UserAccessToken; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.alm.pat.AlmPatDto; -import org.sonar.db.alm.setting.ALM; -import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.server.almintegration.ws.ProjectKeyGenerator; -import org.sonar.server.exceptions.BadConfigurationException; -import org.sonar.server.management.ManagedProjectService; -import org.sonar.server.permission.PermissionService; -import org.sonar.server.permission.PermissionUpdater; -import org.sonar.server.permission.UserPermissionChange; -import org.sonar.server.project.ws.ProjectCreator; -import org.sonar.server.user.UserSession; - -import static java.lang.String.format; -import static java.util.Objects.requireNonNull; -import static org.sonar.core.ce.CeTaskCharacteristics.DEVOPS_PLATFORM_PROJECT_IDENTIFIER; -import static org.sonar.core.ce.CeTaskCharacteristics.DEVOPS_PLATFORM_URL; - -@ServerSide -public class GithubProjectCreatorFactory implements DevOpsProjectCreatorFactory { - private static final Logger LOG = LoggerFactory.getLogger(GithubProjectCreatorFactory.class); - - private final DbClient dbClient; - private final GithubGlobalSettingsValidator githubGlobalSettingsValidator; - private final GithubApplicationClient githubApplicationClient; - private final ProjectKeyGenerator projectKeyGenerator; - private final ProjectCreator projectCreator; - private final UserSession userSession; - private final GitHubSettings gitHubSettings; - private final GithubPermissionConverter githubPermissionConverter; - private final PermissionUpdater permissionUpdater; - private final PermissionService permissionService; - private final ManagedProjectService managedProjectService; - - public GithubProjectCreatorFactory(DbClient dbClient, GithubGlobalSettingsValidator githubGlobalSettingsValidator, - GithubApplicationClient githubApplicationClient, ProjectKeyGenerator projectKeyGenerator, UserSession userSession, - ProjectCreator projectCreator, GitHubSettings gitHubSettings, GithubPermissionConverter githubPermissionConverter, - PermissionUpdater permissionUpdater, PermissionService permissionService, ManagedProjectService managedProjectService) { - this.dbClient = dbClient; - this.githubGlobalSettingsValidator = githubGlobalSettingsValidator; - this.githubApplicationClient = githubApplicationClient; - this.projectKeyGenerator = projectKeyGenerator; - this.userSession = userSession; - this.projectCreator = projectCreator; - this.gitHubSettings = gitHubSettings; - this.githubPermissionConverter = githubPermissionConverter; - this.permissionUpdater = permissionUpdater; - this.permissionService = permissionService; - this.managedProjectService = managedProjectService; - } - - @Override - public Optional getDevOpsProjectCreator(DbSession dbSession, Map characteristics) { - String githubApiUrl = characteristics.get(DEVOPS_PLATFORM_URL); - String githubRepository = characteristics.get(DEVOPS_PLATFORM_PROJECT_IDENTIFIER); - if (githubApiUrl == null || githubRepository == null) { - return Optional.empty(); - } - DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, githubApiUrl, githubRepository); - - return dbClient.almSettingDao().selectByAlm(dbSession, ALM.GITHUB).stream() - .filter(almSettingDto -> devOpsProjectDescriptor.url().equals(almSettingDto.getUrl())) - .map(almSettingDto -> findInstallationIdAndCreateDevOpsProjectCreator(devOpsProjectDescriptor, almSettingDto)) - .flatMap(Optional::stream) - .findFirst(); - - } - - private Optional findInstallationIdAndCreateDevOpsProjectCreator(DevOpsProjectDescriptor devOpsProjectDescriptor, - AlmSettingDto almSettingDto) { - GithubAppConfiguration githubAppConfiguration = githubGlobalSettingsValidator.validate(almSettingDto); - return findInstallationIdToAccessRepo(githubAppConfiguration, devOpsProjectDescriptor.projectIdentifier()) - .map(installationId -> generateAppInstallationToken(githubAppConfiguration, installationId)) - .map(appInstallationToken -> createGithubProjectCreator(devOpsProjectDescriptor, almSettingDto, appInstallationToken)); - } - - private GithubProjectCreator createGithubProjectCreator(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, userSession, appInstallationToken, - authAppInstallationToken.orElse(null)); - return new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, permissionUpdater, permissionService, - managedProjectService, projectCreator, githubProjectCreationParameters, gitHubSettings); - } - - @Override - public Optional getDevOpsProjectCreator(AlmSettingDto almSettingDto, - DevOpsProjectDescriptor devOpsProjectDescriptor) { - if (almSettingDto.getAlm() != ALM.GITHUB) { - return Optional.empty(); - } - try (DbSession dbSession = dbClient.openSession(false)) { - AccessToken accessToken = getAccessToken(dbSession, almSettingDto); - Optional authAppInstallationToken = getAuthAppInstallationTokenIfNecessary(devOpsProjectDescriptor); - GithubProjectCreationParameters githubProjectCreationParameters = new GithubProjectCreationParameters(devOpsProjectDescriptor, almSettingDto, userSession, accessToken, - authAppInstallationToken.orElse(null)); - GithubProjectCreator githubProjectCreator = new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, permissionUpdater, - permissionService, managedProjectService, this.projectCreator, githubProjectCreationParameters, gitHubSettings); - return Optional.of(githubProjectCreator); - } - } - - private AccessToken getAccessToken(DbSession dbSession, AlmSettingDto almSettingDto) { - String userUuid = requireNonNull(userSession.getUuid(), "User UUID cannot be null."); - return dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSettingDto) - .map(AlmPatDto::getPersonalAccessToken) - .map(UserAccessToken::new) - .orElseThrow(() -> new IllegalArgumentException("No personal access token found")); - } - - 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 BadConfigurationException("PROJECT", - 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 created.", - 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/almsettings/ws/gitlab/GitlabProjectCreator.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/gitlab/GitlabProjectCreator.java deleted file mode 100644 index ccf025af59c..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/gitlab/GitlabProjectCreator.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.almsettings.ws.gitlab; - -import java.util.Optional; -import org.jetbrains.annotations.Nullable; -import org.sonar.alm.client.gitlab.GitLabBranch; -import org.sonar.alm.client.gitlab.GitlabApplicationClient; -import org.sonar.alm.client.gitlab.GitlabServerException; -import org.sonar.alm.client.gitlab.Project; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.alm.pat.AlmPatDto; -import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.db.alm.setting.ProjectAlmSettingDto; -import org.sonar.db.project.CreationMethod; -import org.sonar.db.project.ProjectDto; -import org.sonar.server.almintegration.ws.ProjectKeyGenerator; -import org.sonar.server.almsettings.ws.DevOpsProjectCreator; -import org.sonar.server.almsettings.ws.DevOpsProjectDescriptor; -import org.sonar.server.component.ComponentCreationData; -import org.sonar.server.project.ws.ProjectCreator; -import org.sonar.server.user.UserSession; - -import static java.lang.String.format; -import static java.util.Objects.requireNonNull; - -public class GitlabProjectCreator implements DevOpsProjectCreator { - - private final DbClient dbClient; - private final ProjectKeyGenerator projectKeyGenerator; - private final ProjectCreator projectCreator; - private final AlmSettingDto almSettingDto; - private final DevOpsProjectDescriptor devOpsProjectDescriptor; - private final GitlabApplicationClient gitlabApplicationClient; - private final UserSession userSession; - - public GitlabProjectCreator(DbClient dbClient, ProjectKeyGenerator projectKeyGenerator, ProjectCreator projectCreator, AlmSettingDto almSettingDto, - DevOpsProjectDescriptor devOpsProjectDescriptor, GitlabApplicationClient gitlabApplicationClient, UserSession userSession) { - this.dbClient = dbClient; - this.projectKeyGenerator = projectKeyGenerator; - this.projectCreator = projectCreator; - this.almSettingDto = almSettingDto; - this.devOpsProjectDescriptor = devOpsProjectDescriptor; - this.gitlabApplicationClient = gitlabApplicationClient; - this.userSession = userSession; - } - - @Override - public boolean isScanAllowedUsingPermissionsFromDevopsPlatform() { - throw new UnsupportedOperationException("Not Implemented"); - } - - @Override - public ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, CreationMethod creationMethod, Boolean monorepo, @Nullable String projectKey, - @Nullable String projectName) { - - String pat = findPersonalAccessTokenOrThrow(dbSession, almSettingDto); - - String gitlabUrl = requireNonNull(almSettingDto.getUrl(), "DevOps Platform gitlabUrl cannot be null"); - - Long gitlabProjectId = getGitlabProjectId(); - Project gitlabProject = fetchGitlabProject(gitlabUrl, pat, gitlabProjectId); - - Optional almDefaultBranch = getDefaultBranchOnGitlab(gitlabUrl, pat, gitlabProjectId); - ComponentCreationData componentCreationData = projectCreator.createProject( - dbSession, - getProjectKey(projectKey, gitlabProject), - getProjectName(projectName, gitlabProject), - almDefaultBranch.orElse(null), - creationMethod); - ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow(); - - createProjectAlmSettingDto(dbSession, gitlabProjectId.toString(), projectDto, almSettingDto, monorepo); - return componentCreationData; - } - - private String findPersonalAccessTokenOrThrow(DbSession dbSession, AlmSettingDto almSettingDto) { - String userUuid = requireNonNull(userSession.getUuid(), "User UUID cannot be null."); - Optional almPatDto = dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSettingDto); - return almPatDto.map(AlmPatDto::getPersonalAccessToken) - .orElseThrow(() -> new IllegalArgumentException(format("personal access token for '%s' is missing", almSettingDto.getKey()))); - } - - private Long getGitlabProjectId() { - try { - return Long.parseLong(devOpsProjectDescriptor.projectIdentifier()); - } catch (NumberFormatException e) { - throw new IllegalArgumentException(format("GitLab project identifier must be a number, was '%s'", devOpsProjectDescriptor.projectIdentifier())); - } - } - - private Project fetchGitlabProject(String gitlabUrl, String pat, Long gitlabProjectId) { - try { - return gitlabApplicationClient.getProject( - gitlabUrl, - pat, - gitlabProjectId); - } catch (GitlabServerException e) { - throw new IllegalStateException(format("Failed to fetch GitLab project with ID '%s' from '%s'", gitlabProjectId, gitlabUrl), e); - } - } - - private Optional getDefaultBranchOnGitlab(String gitlabUrl, String pat, long gitlabProjectId) { - Optional almMainBranch = gitlabApplicationClient.getBranches(gitlabUrl, pat, gitlabProjectId).stream().filter(GitLabBranch::isDefault).findFirst(); - return almMainBranch.map(GitLabBranch::getName); - } - - private String getProjectKey(@Nullable String projectKey, Project gitlabProject) { - return Optional.ofNullable(projectKey).orElseGet(() -> projectKeyGenerator.generateUniqueProjectKey(gitlabProject.getPathWithNamespace())); - } - - private static String getProjectName(@Nullable String projectName, Project gitlabProject) { - return Optional.ofNullable(projectName).orElse(gitlabProject.getName()); - } - - private void createProjectAlmSettingDto(DbSession dbSession, String gitlabProjectId, ProjectDto projectDto, AlmSettingDto almSettingDto, Boolean monorepo) { - ProjectAlmSettingDto projectAlmSettingDto = new ProjectAlmSettingDto() - .setAlmSettingUuid(almSettingDto.getUuid()) - .setAlmRepo(gitlabProjectId) - .setAlmSlug(null) - .setProjectUuid(projectDto.getUuid()) - .setSummaryCommentEnabled(true) - .setMonorepo(monorepo); - dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, projectAlmSettingDto, almSettingDto.getKey(), projectDto.getName(), projectDto.getKey()); - } - -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/gitlab/GitlabProjectCreatorFactory.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/gitlab/GitlabProjectCreatorFactory.java deleted file mode 100644 index 1e32d828a9a..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/gitlab/GitlabProjectCreatorFactory.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.almsettings.ws.gitlab; - -import java.util.Map; -import java.util.Optional; -import org.sonar.alm.client.gitlab.GitlabApplicationClient; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.alm.setting.ALM; -import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.server.almintegration.ws.ProjectKeyGenerator; -import org.sonar.server.almsettings.ws.DevOpsProjectCreator; -import org.sonar.server.almsettings.ws.DevOpsProjectCreatorFactory; -import org.sonar.server.almsettings.ws.DevOpsProjectDescriptor; -import org.sonar.server.project.ws.ProjectCreator; -import org.sonar.server.user.UserSession; - -public class GitlabProjectCreatorFactory implements DevOpsProjectCreatorFactory { - private final DbClient dbClient; - private final ProjectKeyGenerator projectKeyGenerator; - private final ProjectCreator projectCreator; - private final GitlabApplicationClient gitlabApplicationClient; - private final UserSession userSession; - - public GitlabProjectCreatorFactory(DbClient dbClient, ProjectKeyGenerator projectKeyGenerator, ProjectCreator projectCreator, GitlabApplicationClient gitlabApplicationClient, - UserSession userSession) { - this.dbClient = dbClient; - this.projectKeyGenerator = projectKeyGenerator; - this.projectCreator = projectCreator; - this.gitlabApplicationClient = gitlabApplicationClient; - this.userSession = userSession; - } - - @Override - public Optional getDevOpsProjectCreator(DbSession dbSession, Map characteristics) { - return Optional.empty(); - } - - @Override - public Optional getDevOpsProjectCreator(AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor) { - if (almSettingDto.getAlm() != ALM.GITLAB) { - return Optional.empty(); - } - return Optional.of( - new GitlabProjectCreator( - dbClient, - projectKeyGenerator, - projectCreator, - almSettingDto, - devOpsProjectDescriptor, - gitlabApplicationClient, - userSession)); - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/gitlab/package-info.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/gitlab/package-info.java deleted file mode 100644 index 86fc9b5867f..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/gitlab/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -@ParametersAreNonnullByDefault -package org.sonar.server.almsettings.ws.gitlab; - -import javax.annotation.ParametersAreNonnullByDefault; 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 84de5ba534d..7a86d12ee74 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 @@ -38,14 +38,14 @@ import org.sonar.db.ce.CeTaskTypes; import org.sonar.db.component.BranchDto; import org.sonar.db.component.ComponentDto; import org.sonar.db.permission.GlobalPermission; -import org.sonar.server.almsettings.ws.DevOpsProjectCreator; -import org.sonar.server.almsettings.ws.DevOpsProjectCreatorFactory; +import org.sonar.server.common.almsettings.DevOpsProjectCreator; +import org.sonar.server.common.almsettings.DevOpsProjectCreatorFactory; import org.sonar.server.component.ComponentCreationData; -import org.sonar.server.component.ComponentUpdater; +import org.sonar.server.common.component.ComponentUpdater; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.management.ManagedInstanceService; -import org.sonar.server.permission.PermissionTemplateService; -import org.sonar.server.project.ws.ProjectCreator; +import org.sonar.server.common.permission.PermissionTemplateService; +import org.sonar.server.common.project.ProjectCreator; import org.sonar.server.user.UserSession; import static java.lang.String.format; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentCreationParameters.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentCreationParameters.java deleted file mode 100644 index 84879ca80eb..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentCreationParameters.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.component; - -import javax.annotation.Nullable; -import org.sonar.db.project.CreationMethod; - -public record ComponentCreationParameters(NewComponent newComponent, - @Nullable String userUuid, - @Nullable String userLogin, - @Nullable String mainBranchName, - boolean isManaged, - CreationMethod creationMethod) { - - public static ProjectCreationDataBuilder builder() { - return new ProjectCreationDataBuilder(); - } - - public static final class ProjectCreationDataBuilder { - private NewComponent newComponent; - private String userUuid = null; - private String userLogin = null; - private String mainBranchName = null; - private boolean isManaged = false; - private CreationMethod creationMethod; - - public ProjectCreationDataBuilder newComponent(NewComponent newComponent) { - this.newComponent = newComponent; - return this; - } - - public ProjectCreationDataBuilder userUuid(@Nullable String userUuid) { - this.userUuid = userUuid; - return this; - } - - public ProjectCreationDataBuilder userLogin(@Nullable String userLogin) { - this.userLogin = userLogin; - return this; - } - - public ProjectCreationDataBuilder mainBranchName(@Nullable String mainBranchName) { - this.mainBranchName = mainBranchName; - return this; - } - - public ProjectCreationDataBuilder isManaged(boolean isManaged) { - this.isManaged = isManaged; - return this; - } - - public ProjectCreationDataBuilder creationMethod(CreationMethod creationMethod) { - this.creationMethod = creationMethod; - return this; - } - - public ComponentCreationParameters build() { - return new ComponentCreationParameters(newComponent, userUuid, userLogin, mainBranchName, isManaged, creationMethod); - } - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java deleted file mode 100644 index 18ede19a08f..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.component; - -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import javax.annotation.Nullable; -import org.sonar.api.resources.Qualifiers; -import org.sonar.api.resources.Scopes; -import org.sonar.api.utils.System2; -import org.sonar.core.i18n.I18n; -import org.sonar.core.util.UuidFactory; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.component.BranchDto; -import org.sonar.db.component.BranchType; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.portfolio.PortfolioDto; -import org.sonar.db.portfolio.PortfolioDto.SelectionMode; -import org.sonar.db.project.CreationMethod; -import org.sonar.db.project.ProjectDto; -import org.sonar.db.user.UserDto; -import org.sonar.server.es.Indexers; -import org.sonar.server.favorite.FavoriteUpdater; -import org.sonar.server.common.permission.Operation; -import org.sonar.server.permission.PermissionService; -import org.sonar.server.permission.PermissionTemplateService; -import org.sonar.server.permission.PermissionUpdater; -import org.sonar.server.permission.UserPermissionChange; -import org.sonar.server.project.DefaultBranchNameResolver; - -import static com.google.common.base.Preconditions.checkState; -import static java.util.Collections.singletonList; -import static org.sonar.api.web.UserRole.PUBLIC_PERMISSIONS; -import static org.sonar.core.component.ComponentKeys.ALLOWED_CHARACTERS_MESSAGE; -import static org.sonar.core.component.ComponentKeys.isValidProjectKey; -import static org.sonar.server.exceptions.BadRequestException.checkRequest; -import static org.sonar.server.exceptions.BadRequestException.throwBadRequestException; - -public class ComponentUpdater { - - private static final Set PROJ_APP_QUALIFIERS = Set.of(Qualifiers.PROJECT, Qualifiers.APP); - private static final String KEY_ALREADY_EXISTS_ERROR = "Could not create %s with key: \"%s\". A similar key already exists: \"%s\""; - private static final String MALFORMED_KEY_ERROR = "Malformed key for %s: '%s'. %s."; - private final DbClient dbClient; - private final I18n i18n; - private final System2 system2; - private final PermissionTemplateService permissionTemplateService; - private final FavoriteUpdater favoriteUpdater; - private final Indexers indexers; - private final UuidFactory uuidFactory; - private final DefaultBranchNameResolver defaultBranchNameResolver; - private final PermissionUpdater userPermissionUpdater; - private final PermissionService permissionService; - - public ComponentUpdater(DbClient dbClient, I18n i18n, System2 system2, - PermissionTemplateService permissionTemplateService, FavoriteUpdater favoriteUpdater, - Indexers indexers, UuidFactory uuidFactory, DefaultBranchNameResolver defaultBranchNameResolver, PermissionUpdater userPermissionUpdater, - PermissionService permissionService) { - this.dbClient = dbClient; - this.i18n = i18n; - this.system2 = system2; - this.permissionTemplateService = permissionTemplateService; - this.favoriteUpdater = favoriteUpdater; - this.indexers = indexers; - this.uuidFactory = uuidFactory; - this.defaultBranchNameResolver = defaultBranchNameResolver; - this.userPermissionUpdater = userPermissionUpdater; - this.permissionService = permissionService; - } - - /** - * - Create component - * - Apply default permission template - * - Add component to favorite if the component has the 'Project Creators' permission - * - Index component in es indexes - */ - public ComponentCreationData create(DbSession dbSession, ComponentCreationParameters componentCreationParameters) { - ComponentCreationData componentCreationData = createWithoutCommit(dbSession, componentCreationParameters); - commitAndIndex(dbSession, componentCreationData); - return componentCreationData; - } - - public void commitAndIndex(DbSession dbSession, ComponentCreationData componentCreationData) { - if (componentCreationData.portfolioDto() != null) { - indexers.commitAndIndexEntities(dbSession, singletonList(componentCreationData.portfolioDto()), Indexers.EntityEvent.CREATION); - } else if (componentCreationData.projectDto() != null) { - indexers.commitAndIndexEntities(dbSession, singletonList(componentCreationData.projectDto()), Indexers.EntityEvent.CREATION); - } - } - - /** - * Create component without committing. - * Don't forget to call commitAndIndex(...) when ready to commit. - */ - public ComponentCreationData createWithoutCommit(DbSession dbSession, ComponentCreationParameters componentCreationParameters) { - checkKeyFormat(componentCreationParameters.newComponent().qualifier(), componentCreationParameters.newComponent().key()); - checkKeyAlreadyExists(dbSession, componentCreationParameters.newComponent()); - - long now = system2.now(); - - ComponentDto componentDto = createRootComponent(dbSession, componentCreationParameters.newComponent(), now); - - BranchDto mainBranch = null; - ProjectDto projectDto = null; - PortfolioDto portfolioDto = null; - - if (isProjectOrApp(componentDto)) { - projectDto = toProjectDto(componentDto, now, componentCreationParameters.creationMethod()); - dbClient.projectDao().insert(dbSession, projectDto); - addToFavourites(dbSession, projectDto, componentCreationParameters.userUuid(), componentCreationParameters.userLogin()); - mainBranch = createMainBranch(dbSession, componentDto.uuid(), projectDto.getUuid(), componentCreationParameters.mainBranchName()); - if (componentCreationParameters.isManaged()) { - applyPublicPermissionsForCreator(dbSession, projectDto, componentCreationParameters.userUuid()); - } else { - permissionTemplateService.applyDefaultToNewComponent(dbSession, projectDto, componentCreationParameters.userUuid()); - } - } else if (isPortfolio(componentDto)) { - portfolioDto = toPortfolioDto(componentDto, now); - dbClient.portfolioDao().insert(dbSession, portfolioDto, false); - permissionTemplateService.applyDefaultToNewComponent(dbSession, portfolioDto, componentCreationParameters.userUuid()); - } else { - throw new IllegalArgumentException("Component " + componentDto + " is not a top level entity"); - } - - return new ComponentCreationData(componentDto, portfolioDto, mainBranch, projectDto); - } - - private void applyPublicPermissionsForCreator(DbSession dbSession, ProjectDto projectDto, @Nullable String userUuid) { - if (userUuid != null) { - UserDto userDto = dbClient.userDao().selectByUuid(dbSession, userUuid); - checkState(userDto != null, "User with uuid '%s' doesn't exist", userUuid); - userPermissionUpdater.apply(dbSession, - PUBLIC_PERMISSIONS.stream() - .map(permission -> toUserPermissionChange(permission, projectDto, userDto)) - .collect(Collectors.toSet())); - } - } - - private UserPermissionChange toUserPermissionChange(String permission, ProjectDto projectDto, UserDto userDto) { - return new UserPermissionChange(Operation.ADD, permission, projectDto, userDto, permissionService); - } - - private void addToFavourites(DbSession dbSession, ProjectDto projectDto, @Nullable String userUuid, @Nullable String userLogin) { - if (permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(dbSession, projectDto)) { - favoriteUpdater.add(dbSession, projectDto, userUuid, userLogin, false); - } - } - - private void checkKeyFormat(String qualifier, String key) { - checkRequest(isValidProjectKey(key), MALFORMED_KEY_ERROR, getQualifierToDisplay(qualifier), key, ALLOWED_CHARACTERS_MESSAGE); - } - - private void checkKeyAlreadyExists(DbSession dbSession, NewComponent newComponent) { - List componentDtos = dbClient.componentDao().selectByKeyCaseInsensitive(dbSession, newComponent.key()); - - if (!componentDtos.isEmpty()) { - String alreadyExistingKeys = componentDtos - .stream() - .map(ComponentDto::getKey) - .collect(Collectors.joining(", ")); - throwBadRequestException(KEY_ALREADY_EXISTS_ERROR, getQualifierToDisplay(newComponent.qualifier()), newComponent.key(), alreadyExistingKeys); - } - } - - private ComponentDto createRootComponent(DbSession session, NewComponent newComponent, long now) { - String uuid = uuidFactory.create(); - - ComponentDto component = new ComponentDto() - .setUuid(uuid) - .setUuidPath(ComponentDto.UUID_PATH_OF_ROOT) - .setBranchUuid(uuid) - .setKey(newComponent.key()) - .setName(newComponent.name()) - .setDescription(newComponent.description()) - .setLongName(newComponent.name()) - .setScope(Scopes.PROJECT) - .setQualifier(newComponent.qualifier()) - .setPrivate(newComponent.isPrivate()) - .setCreatedAt(new Date(now)); - - dbClient.componentDao().insert(session, component, true); - return component; - } - - private ProjectDto toProjectDto(ComponentDto component, long now, CreationMethod creationMethod) { - return new ProjectDto() - .setUuid(uuidFactory.create()) - .setKey(component.getKey()) - .setQualifier(component.qualifier()) - .setName(component.name()) - .setPrivate(component.isPrivate()) - .setDescription(component.description()) - .setCreationMethod(creationMethod) - .setUpdatedAt(now) - .setCreatedAt(now); - } - - private static PortfolioDto toPortfolioDto(ComponentDto component, long now) { - return new PortfolioDto() - .setUuid(component.uuid()) - .setRootUuid(component.branchUuid()) - .setKey(component.getKey()) - .setName(component.name()) - .setPrivate(component.isPrivate()) - .setDescription(component.description()) - .setSelectionMode(SelectionMode.NONE.name()) - .setUpdatedAt(now) - .setCreatedAt(now); - } - - private static boolean isProjectOrApp(ComponentDto componentDto) { - return PROJ_APP_QUALIFIERS.contains(componentDto.qualifier()); - } - - private static boolean isPortfolio(ComponentDto componentDto) { - return Qualifiers.VIEW.contains(componentDto.qualifier()); - } - - private BranchDto createMainBranch(DbSession session, String componentUuid, String projectUuid, @Nullable String mainBranch) { - BranchDto branch = new BranchDto() - .setBranchType(BranchType.BRANCH) - .setUuid(componentUuid) - .setIsMain(true) - .setKey(Optional.ofNullable(mainBranch).orElse(defaultBranchNameResolver.getEffectiveMainBranchName())) - .setMergeBranchUuid(null) - .setExcludeFromPurge(true) - .setProjectUuid(projectUuid); - dbClient.branchDao().upsert(session, branch); - return branch; - } - - private String getQualifierToDisplay(String qualifier) { - return i18n.message(Locale.getDefault(), "qualifier." + qualifier, "Project"); - } - -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/NewComponent.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/NewComponent.java deleted file mode 100644 index 3429bb5b953..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/NewComponent.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.component; - -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; - -import static org.sonar.api.resources.Qualifiers.PROJECT; -import static org.sonar.db.component.ComponentValidator.checkComponentKey; -import static org.sonar.db.component.ComponentValidator.checkComponentName; -import static org.sonar.db.component.ComponentValidator.checkComponentQualifier; - -@Immutable -public class NewComponent { - private final String key; - private final String qualifier; - private final String name; - private final String description; - private final boolean isPrivate; - - private NewComponent(NewComponent.Builder builder) { - this.key = builder.key; - this.qualifier = builder.qualifier; - this.name = builder.name; - this.isPrivate = builder.isPrivate; - this.description = builder.description; - } - - public static Builder newComponentBuilder() { - return new Builder(); - } - - public String key() { - return key; - } - - public String name() { - return name; - } - - public String qualifier() { - return qualifier; - } - - public boolean isPrivate() { - return isPrivate; - } - - @CheckForNull - public String description() { - return description; - } - - public boolean isProject() { - return PROJECT.equals(qualifier); - } - - public static class Builder { - private String description; - private String key; - private String qualifier = PROJECT; - private String name; - private boolean isPrivate = false; - - private Builder() { - // use static factory method newComponentBuilder() - } - - public Builder setKey(String key) { - this.key = key; - return this; - } - - public Builder setQualifier(String qualifier) { - this.qualifier = qualifier; - return this; - } - - public Builder setName(String name) { - this.name = name; - return this; - } - - public Builder setPrivate(boolean isPrivate) { - this.isPrivate = isPrivate; - return this; - } - - public Builder setDescription(@Nullable String description) { - this.description = description; - return this; - } - - public NewComponent build() { - checkComponentKey(key); - checkComponentName(name); - checkComponentQualifier(qualifier); - return new NewComponent(this); - } - } - -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/CaycUtils.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/CaycUtils.java deleted file mode 100644 index f2ef6a38f83..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/CaycUtils.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.newcodeperiod; - -import org.sonar.db.newcodeperiod.NewCodePeriodType; - -public interface CaycUtils { - static boolean isNewCodePeriodCompliant(NewCodePeriodType type, String value) { - if (type == NewCodePeriodType.NUMBER_OF_DAYS) { - return parseDays(value) > 0 && parseDays(value) <= 90; - } - return true; - } - - static int parseDays(String value) { - try { - return Integer.parseInt(value); - } catch (Exception e) { - throw new IllegalArgumentException("Failed to parse number of days: " + value); - } - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/NewCodeDefinitionResolver.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/NewCodeDefinitionResolver.java deleted file mode 100644 index 12491a1b0a4..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/NewCodeDefinitionResolver.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.newcodeperiod; - -import com.google.common.base.Preconditions; -import java.util.EnumSet; -import java.util.Locale; -import java.util.Optional; -import javax.annotation.Nullable; -import org.sonar.core.platform.EditionProvider; -import org.sonar.core.platform.PlatformEditionProvider; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.newcodeperiod.NewCodePeriodDto; -import org.sonar.db.newcodeperiod.NewCodePeriodParser; -import org.sonar.db.newcodeperiod.NewCodePeriodType; - -import static org.sonar.db.newcodeperiod.NewCodePeriodType.NUMBER_OF_DAYS; -import static org.sonar.db.newcodeperiod.NewCodePeriodType.PREVIOUS_VERSION; -import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH; - -public class NewCodeDefinitionResolver { - private static final String BEGIN_LIST = "
      "; - - private static final String END_LIST = "
    "; - private static final String BEGIN_ITEM_LIST = "
  • "; - private static final String END_ITEM_LIST = "
  • "; - - public static final String NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION = "Project New Code Definition Type
    " + - "New code definitions of the following types are allowed:" + - BEGIN_LIST + - BEGIN_ITEM_LIST + PREVIOUS_VERSION.name() + END_ITEM_LIST + - BEGIN_ITEM_LIST + NUMBER_OF_DAYS.name() + END_ITEM_LIST + - BEGIN_ITEM_LIST + REFERENCE_BRANCH.name() + " - will default to the main branch." + END_ITEM_LIST + - END_LIST; - - public static final String NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION = "Project New Code Definition Value
    " + - "For each new code definition type, a different value is expected:" + - BEGIN_LIST + - BEGIN_ITEM_LIST + "no value, when the new code definition type is " + PREVIOUS_VERSION.name() + " and " + REFERENCE_BRANCH.name() + END_ITEM_LIST + - BEGIN_ITEM_LIST + "a number between 1 and 90, when the new code definition type is " + NUMBER_OF_DAYS.name() + END_ITEM_LIST + - END_LIST; - - private static final String UNEXPECTED_VALUE_ERROR_MESSAGE = "Unexpected value for newCodeDefinitionType '%s'"; - - private static final EnumSet projectCreationNCDTypes = EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS, REFERENCE_BRANCH); - - private final DbClient dbClient; - private final PlatformEditionProvider editionProvider; - - public NewCodeDefinitionResolver(DbClient dbClient, PlatformEditionProvider editionProvider) { - this.dbClient = dbClient; - this.editionProvider = editionProvider; - } - - public void createNewCodeDefinition(DbSession dbSession, String projectUuid, String mainBranchUuid, - String defaultBranchName, String newCodeDefinitionType, @Nullable String newCodeDefinitionValue) { - - boolean isCommunityEdition = editionProvider.get().filter(EditionProvider.Edition.COMMUNITY::equals).isPresent(); - NewCodePeriodType newCodePeriodType = parseNewCodeDefinitionType(newCodeDefinitionType); - - NewCodePeriodDto dto = new NewCodePeriodDto(); - dto.setType(newCodePeriodType); - dto.setProjectUuid(projectUuid); - - if (isCommunityEdition) { - dto.setBranchUuid(mainBranchUuid); - } - - getNewCodeDefinitionValueProjectCreation(newCodePeriodType, newCodeDefinitionValue, defaultBranchName).ifPresent(dto::setValue); - - if (!CaycUtils.isNewCodePeriodCompliant(dto.getType(), dto.getValue())) { - throw new IllegalArgumentException("Failed to set the New Code Definition. The given value is not compatible with the Clean as You Code methodology. " - + "Please refer to the documentation for compliant options."); - } - - dbClient.newCodePeriodDao().insert(dbSession, dto); - } - - public static void checkNewCodeDefinitionParam(@Nullable String newCodeDefinitionType, @Nullable String newCodeDefinitionValue) { - if (newCodeDefinitionType == null && newCodeDefinitionValue != null) { - throw new IllegalArgumentException("New code definition type is required when new code definition value is provided"); - } - } - - private static Optional getNewCodeDefinitionValueProjectCreation(NewCodePeriodType type, @Nullable String value, String defaultBranchName) { - return switch (type) { - case PREVIOUS_VERSION -> { - Preconditions.checkArgument(value == null, UNEXPECTED_VALUE_ERROR_MESSAGE, type); - yield Optional.empty(); - } - case NUMBER_OF_DAYS -> { - requireValue(type, value); - yield Optional.of(parseDays(value)); - } - case REFERENCE_BRANCH -> { - Preconditions.checkArgument(value == null, UNEXPECTED_VALUE_ERROR_MESSAGE, type); - yield Optional.of(defaultBranchName); - } - default -> throw new IllegalStateException("Unexpected type: " + type); - }; - } - - private static String parseDays(String value) { - try { - return Integer.toString(NewCodePeriodParser.parseDays(value)); - } catch (Exception e) { - throw new IllegalArgumentException("Failed to parse number of days: " + value); - } - } - - private static void requireValue(NewCodePeriodType type, @Nullable String value) { - Preconditions.checkArgument(value != null, "New code definition type '%s' requires a newCodeDefinitionValue", type); - } - - private static NewCodePeriodType parseNewCodeDefinitionType(String typeStr) { - NewCodePeriodType type; - try { - type = NewCodePeriodType.valueOf(typeStr.toUpperCase(Locale.US)); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Invalid type: " + typeStr); - } - validateType(type); - return type; - } - - private static void validateType(NewCodePeriodType type) { - Preconditions.checkArgument(projectCreationNCDTypes.contains(type), "Invalid type '%s'. `newCodeDefinitionType` can only be set with types: %s", - type, projectCreationNCDTypes); - } - -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/SetAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/SetAction.java index 60c581f6acc..dc5a067e5ba 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/SetAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/SetAction.java @@ -42,7 +42,7 @@ import org.sonar.db.newcodeperiod.NewCodePeriodType; import org.sonar.db.project.ProjectDto; import org.sonar.server.component.ComponentFinder; import org.sonar.server.exceptions.NotFoundException; -import org.sonar.server.newcodeperiod.CaycUtils; +import org.sonar.server.common.newcodeperiod.CaycUtils; import org.sonar.server.user.UserSession; import static com.google.common.base.Preconditions.checkArgument; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/UnsetAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/UnsetAction.java index 92609739a83..b656e4c2e23 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/UnsetAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/UnsetAction.java @@ -34,7 +34,7 @@ import org.sonar.db.newcodeperiod.NewCodePeriodDao; import org.sonar.db.project.ProjectDto; import org.sonar.server.component.ComponentFinder; import org.sonar.server.exceptions.NotFoundException; -import org.sonar.server.newcodeperiod.CaycUtils; +import org.sonar.server.common.newcodeperiod.CaycUtils; import org.sonar.server.user.UserSession; import static java.lang.String.format; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/DefaultTemplatesResolver.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/DefaultTemplatesResolver.java deleted file mode 100644 index 016134781c4..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/DefaultTemplatesResolver.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.permission; - -import java.util.Optional; -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; -import org.sonar.db.DbSession; - -import static java.util.Objects.requireNonNull; -import static java.util.Optional.ofNullable; - -public interface DefaultTemplatesResolver { - /** - * Resolve the effective default templates uuid for the specified {@link DefaultTemplates}. - *
      - *
    • {@link ResolvedDefaultTemplates#project} is always the same as {@link DefaultTemplates#projectUuid}
    • - *
    • when Governance is not installed, {@link ResolvedDefaultTemplates#application} is always {@code null}
    • - *
    • when Governance is installed, {@link ResolvedDefaultTemplates#application} is {@link DefaultTemplates#applicationsUuid} - * when it is non {@code null}, otherwise it is {@link DefaultTemplates#projectUuid}
    • - *
    • when Governance is installed, {@link ResolvedDefaultTemplates#portfolio} is {@link DefaultTemplates#portfoliosUuid} - * when it is non {@code null}, otherwise it is {@link DefaultTemplates#projectUuid}
    • - *
    - */ - ResolvedDefaultTemplates resolve(DbSession dbSession); - - @Immutable - final class ResolvedDefaultTemplates { - private final String project; - private final String application; - private final String portfolio; - - public ResolvedDefaultTemplates(String project, @Nullable String application, @Nullable String portfolio) { - this.project = requireNonNull(project, "project can't be null"); - this.application = application; - this.portfolio = portfolio; - } - - public String getProject() { - return project; - } - - public Optional getApplication() { - return ofNullable(application); - } - - public Optional getPortfolio() { - return ofNullable(portfolio); - } - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/DefaultTemplatesResolverImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/DefaultTemplatesResolverImpl.java deleted file mode 100644 index d1c62f96925..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/DefaultTemplatesResolverImpl.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.permission; - -import org.sonar.api.resources.Qualifiers; -import org.sonar.api.resources.ResourceType; -import org.sonar.api.resources.ResourceTypes; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.server.property.InternalProperties; - -public class DefaultTemplatesResolverImpl implements DefaultTemplatesResolver { - - private final DbClient dbClient; - private final ResourceTypes resourceTypes; - - public DefaultTemplatesResolverImpl(DbClient dbClient, ResourceTypes resourceTypes) { - this.dbClient = dbClient; - this.resourceTypes = resourceTypes; - } - - @Override - public ResolvedDefaultTemplates resolve(DbSession dbSession) { - String defaultProjectTemplate = dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.DEFAULT_PROJECT_TEMPLATE).orElseThrow(() -> { - throw new IllegalStateException("Default template for project is missing"); - }); - - String defaultPortfolioTemplate = null; - String defaultApplicationTemplate = null; - - if (isPortfolioEnabled(resourceTypes)) { - defaultPortfolioTemplate = dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.DEFAULT_PORTFOLIO_TEMPLATE).orElse(defaultProjectTemplate); - } - if (isApplicationEnabled(resourceTypes)) { - defaultApplicationTemplate = dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.DEFAULT_APPLICATION_TEMPLATE).orElse(defaultProjectTemplate); - } - return new ResolvedDefaultTemplates(defaultProjectTemplate, defaultApplicationTemplate, defaultPortfolioTemplate); - } - - private static boolean isPortfolioEnabled(ResourceTypes resourceTypes) { - return resourceTypes.getRoots() - .stream() - .map(ResourceType::getQualifier) - .anyMatch(Qualifiers.VIEW::equals); - } - - private static boolean isApplicationEnabled(ResourceTypes resourceTypes) { - return resourceTypes.getRoots() - .stream() - .map(ResourceType::getQualifier) - .anyMatch(Qualifiers.APP::equals); - } - -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/GranteeTypeSpecificPermissionUpdater.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/GranteeTypeSpecificPermissionUpdater.java deleted file mode 100644 index 7965a5a4252..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/GranteeTypeSpecificPermissionUpdater.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.permission; - -import java.util.Set; -import javax.annotation.Nullable; -import org.sonar.db.DbSession; - -interface GranteeTypeSpecificPermissionUpdater { - Class getHandledClass(); - - Set loadExistingEntityPermissions(DbSession dbSession, String uuidOfGrantee, @Nullable String entityUuid); - - boolean apply(DbSession dbSession, Set existingPermissions, T change); -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/GroupPermissionChange.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/GroupPermissionChange.java deleted file mode 100644 index 18b92113a20..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/GroupPermissionChange.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.permission; - -import java.util.Optional; -import javax.annotation.Nullable; -import org.sonar.db.entity.EntityDto; -import org.sonar.db.user.GroupDto; -import org.sonar.server.common.permission.Operation; - -public class GroupPermissionChange extends PermissionChange { - - private final GroupDto groupDto; - - public GroupPermissionChange(Operation operation, String permission, @Nullable EntityDto entityDto, - @Nullable GroupDto groupDto, PermissionService permissionService) { - super(operation, permission, entityDto, permissionService); - this.groupDto = groupDto; - } - - public GroupUuidOrAnyone getGroupUuidOrAnyone() { - return GroupUuidOrAnyone.from(groupDto); - } - - public Optional getGroupName() { - return Optional.ofNullable(groupDto).map(GroupDto::getName); - } - - @Override - public String getUuidOfGrantee() { - return getGroupUuidOrAnyone().getUuid(); - } - - -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/GroupPermissionChanger.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/GroupPermissionChanger.java deleted file mode 100644 index 004b6c0d364..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/GroupPermissionChanger.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.permission; - -import java.util.HashSet; -import java.util.Set; -import javax.annotation.Nullable; -import org.sonar.api.web.UserRole; -import org.sonar.core.util.UuidFactory; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.entity.EntityDto; -import org.sonar.db.permission.GlobalPermission; -import org.sonar.db.permission.GroupPermissionDto; - -import static com.google.common.base.Preconditions.checkNotNull; -import static java.lang.String.format; -import static org.sonar.server.exceptions.BadRequestException.checkRequest; -import static org.sonar.server.common.permission.Operation.ADD; -import static org.sonar.server.common.permission.Operation.REMOVE; - -public class GroupPermissionChanger implements GranteeTypeSpecificPermissionUpdater { - - private final DbClient dbClient; - private final UuidFactory uuidFactory; - - public GroupPermissionChanger(DbClient dbClient, UuidFactory uuidFactory) { - this.dbClient = dbClient; - this.uuidFactory = uuidFactory; - } - - @Override - public Class getHandledClass() { - return GroupPermissionChange.class; - } - - @Override - public Set loadExistingEntityPermissions(DbSession dbSession, String uuidOfGrantee, @Nullable String entityUuid) { - if (entityUuid != null) { - return new HashSet<>(dbClient.groupPermissionDao().selectEntityPermissionsOfGroup(dbSession, uuidOfGrantee, entityUuid)); - } - return new HashSet<>(dbClient.groupPermissionDao().selectGlobalPermissionsOfGroup(dbSession, uuidOfGrantee)); - } - - @Override - public boolean apply(DbSession dbSession, Set existingPermissions, GroupPermissionChange change) { - ensureConsistencyWithVisibility(change); - if (isImplicitlyAlreadyDone(change)) { - return false; - } - switch (change.getOperation()) { - case ADD: - if (existingPermissions.contains(change.getPermission())) { - return false; - } - return addPermission(dbSession, change); - case REMOVE: - if (!existingPermissions.contains(change.getPermission())) { - return false; - } - return removePermission(dbSession, change); - default: - throw new UnsupportedOperationException("Unsupported permission change: " + change.getOperation()); - } - } - - private static boolean isImplicitlyAlreadyDone(GroupPermissionChange change) { - EntityDto project = change.getEntity(); - if (project != null) { - return isImplicitlyAlreadyDone(project, change); - } - return false; - } - - private static boolean isImplicitlyAlreadyDone(EntityDto project, GroupPermissionChange change) { - return isAttemptToAddPublicPermissionToPublicComponent(change, project) - || isAttemptToRemovePermissionFromAnyoneOnPrivateComponent(change, project); - } - - private static boolean isAttemptToAddPublicPermissionToPublicComponent(GroupPermissionChange change, EntityDto project) { - return !project.isPrivate() - && change.getOperation() == ADD - && UserRole.PUBLIC_PERMISSIONS.contains(change.getPermission()); - } - - private static boolean isAttemptToRemovePermissionFromAnyoneOnPrivateComponent(GroupPermissionChange change, EntityDto project) { - return project.isPrivate() - && change.getOperation() == REMOVE - && change.getGroupUuidOrAnyone().isAnyone(); - } - - private static void ensureConsistencyWithVisibility(GroupPermissionChange change) { - EntityDto project = change.getEntity(); - if (project != null) { - checkRequest( - !isAttemptToAddPermissionToAnyoneOnPrivateComponent(change, project), - "No permission can be granted to Anyone on a private component"); - checkRequest( - !isAttemptToRemovePublicPermissionFromPublicComponent(change, project), - "Permission %s can't be removed from a public component", change.getPermission()); - } - } - - private static boolean isAttemptToAddPermissionToAnyoneOnPrivateComponent(GroupPermissionChange change, EntityDto project) { - return project.isPrivate() - && change.getOperation() == ADD - && change.getGroupUuidOrAnyone().isAnyone(); - } - - private static boolean isAttemptToRemovePublicPermissionFromPublicComponent(GroupPermissionChange change, EntityDto project) { - return !project.isPrivate() - && change.getOperation() == REMOVE - && UserRole.PUBLIC_PERMISSIONS.contains(change.getPermission()); - } - - private boolean addPermission(DbSession dbSession, GroupPermissionChange change) { - validateNotAnyoneAndAdminPermission(change.getPermission(), change.getGroupUuidOrAnyone()); - - String groupUuid = change.getGroupUuidOrAnyone().getUuid(); - String groupName = change.getGroupName().orElse(null); - - GroupPermissionDto addedDto = new GroupPermissionDto() - .setUuid(uuidFactory.create()) - .setRole(change.getPermission()) - .setGroupUuid(groupUuid) - .setEntityName(change.getProjectName()) - .setEntityUuid(change.getProjectUuid()) - .setGroupName(groupName); - - dbClient.groupPermissionDao().insert(dbSession, addedDto, change.getEntity(), null); - return true; - } - - private static void validateNotAnyoneAndAdminPermission(String permission, GroupUuidOrAnyone group) { - checkRequest(!GlobalPermission.ADMINISTER.getKey().equals(permission) || !group.isAnyone(), - format("It is not possible to add the '%s' permission to group 'Anyone'.", permission)); - } - - private boolean removePermission(DbSession dbSession, GroupPermissionChange change) { - checkIfRemainingGlobalAdministrators(dbSession, change); - String groupUuid = change.getGroupUuidOrAnyone().getUuid(); - String groupName = change.getGroupName().orElse(null); - dbClient.groupPermissionDao().delete(dbSession, - change.getPermission(), - groupUuid, - groupName, - change.getEntity()); - return true; - } - - private void checkIfRemainingGlobalAdministrators(DbSession dbSession, GroupPermissionChange change) { - GroupUuidOrAnyone groupUuidOrAnyone = change.getGroupUuidOrAnyone(); - if (GlobalPermission.ADMINISTER.getKey().equals(change.getPermission()) && - !groupUuidOrAnyone.isAnyone() && - change.getProjectUuid() == null) { - String groupUuid = checkNotNull(groupUuidOrAnyone.getUuid()); - // removing global admin permission from group - int remaining = dbClient.authorizationDao().countUsersWithGlobalPermissionExcludingGroup(dbSession, GlobalPermission.ADMINISTER.getKey(), groupUuid); - checkRequest(remaining > 0, "Last group with permission '%s'. Permission cannot be removed.", GlobalPermission.ADMINISTER.getKey()); - } - } - -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/PermissionChange.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/PermissionChange.java deleted file mode 100644 index 1e96e721fcc..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/PermissionChange.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.permission; - -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import org.sonar.db.entity.EntityDto; -import org.sonar.db.permission.GlobalPermission; -import org.sonar.server.common.permission.Operation; - -import static java.util.Objects.requireNonNull; -import static org.sonar.server.exceptions.BadRequestException.checkRequest; - -public abstract class PermissionChange { - - private final Operation operation; - private final String permission; - private final EntityDto entity; - protected final PermissionService permissionService; - - protected PermissionChange(Operation operation, String permission, @Nullable EntityDto entity, PermissionService permissionService) { - this.operation = requireNonNull(operation); - this.permission = requireNonNull(permission); - this.entity = entity; - this.permissionService = permissionService; - if (entity == null) { - checkRequest(permissionService.getGlobalPermissions().stream().anyMatch(p -> p.getKey().equals(permission)), - "Invalid global permission '%s'. Valid values are %s", permission, - permissionService.getGlobalPermissions().stream().map(GlobalPermission::getKey).toList()); - } else { - checkRequest(permissionService.getAllProjectPermissions().contains(permission), "Invalid project permission '%s'. Valid values are %s", permission, - permissionService.getAllProjectPermissions()); - } - } - - public Operation getOperation() { - return operation; - } - - public String getPermission() { - return permission; - } - - @CheckForNull - public EntityDto getEntity() { - return entity; - } - - @CheckForNull - public String getProjectName() { - return entity == null ? null : entity.getName(); - } - - @CheckForNull - public String getProjectUuid() { - return entity == null ? null : entity.getUuid(); - } - - public abstract String getUuidOfGrantee(); -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/PermissionTemplateService.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/PermissionTemplateService.java deleted file mode 100644 index 2b2bd606ada..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/PermissionTemplateService.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.permission; - -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import org.apache.commons.lang3.StringUtils; -import org.sonar.api.resources.Qualifiers; -import org.sonar.api.server.ServerSide; -import org.sonar.core.util.UuidFactory; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.entity.EntityDto; -import org.sonar.db.permission.GroupPermissionDto; -import org.sonar.db.permission.UserPermissionDto; -import org.sonar.db.permission.template.PermissionTemplateCharacteristicDto; -import org.sonar.db.permission.template.PermissionTemplateDto; -import org.sonar.db.permission.template.PermissionTemplateGroupDto; -import org.sonar.db.permission.template.PermissionTemplateUserDto; -import org.sonar.db.project.ProjectDto; -import org.sonar.db.user.UserDto; -import org.sonar.db.user.UserId; -import org.sonar.server.es.Indexers; -import org.sonar.server.exceptions.TemplateMatchingKeyException; -import org.sonar.server.permission.DefaultTemplatesResolver.ResolvedDefaultTemplates; -import org.sonar.server.user.UserSession; - -import static com.google.common.base.Preconditions.checkArgument; -import static java.lang.String.format; -import static java.util.Collections.singletonList; -import static org.sonar.api.security.DefaultGroups.isAnyone; -import static org.sonar.api.web.UserRole.PUBLIC_PERMISSIONS; -import static org.sonar.db.permission.GlobalPermission.SCAN; - -@ServerSide -public class PermissionTemplateService { - - private final DbClient dbClient; - private final Indexers indexers; - private final UserSession userSession; - private final DefaultTemplatesResolver defaultTemplatesResolver; - private final UuidFactory uuidFactory; - - public PermissionTemplateService(DbClient dbClient, Indexers indexers, UserSession userSession, - DefaultTemplatesResolver defaultTemplatesResolver, UuidFactory uuidFactory) { - this.dbClient = dbClient; - this.indexers = indexers; - this.userSession = userSession; - this.defaultTemplatesResolver = defaultTemplatesResolver; - this.uuidFactory = uuidFactory; - } - - public boolean wouldUserHaveScanPermissionWithDefaultTemplate(DbSession dbSession, @Nullable String userUuid, String projectKey) { - if (userSession.hasPermission(SCAN)) { - return true; - } - - ProjectDto projectDto = new ProjectDto().setKey(projectKey).setQualifier(Qualifiers.PROJECT); - PermissionTemplateDto template = findTemplate(dbSession, projectDto); - if (template == null) { - return false; - } - - List potentialPermissions = dbClient.permissionTemplateDao().selectPotentialPermissionsByUserUuidAndTemplateUuid(dbSession, userUuid, template.getUuid()); - return potentialPermissions.contains(SCAN.getKey()); - } - - /** - * Apply a permission template to a set of projects. Authorization to administrate these projects - * is not verified. The projects must exist, so the "project creator" permissions defined in the - * template are ignored. - */ - public void applyAndCommit(DbSession dbSession, PermissionTemplateDto template, Collection entities) { - if (entities.isEmpty()) { - return; - } - - for (EntityDto entity : entities) { - dbClient.groupPermissionDao().deleteByEntityUuid(dbSession, entity); - dbClient.userPermissionDao().deleteEntityPermissions(dbSession, entity); - copyPermissions(dbSession, template, entity, null); - } - indexers.commitAndIndexEntities(dbSession, entities, Indexers.EntityEvent.PERMISSION_CHANGE); - } - - /** - * Apply the default permission template to a new project (has no permissions yet). - * - * @param projectCreatorUserId id of the user creating the project. - */ - public void applyDefaultToNewComponent(DbSession dbSession, EntityDto entityDto, @Nullable String projectCreatorUserId) { - PermissionTemplateDto template = findTemplate(dbSession, entityDto); - checkArgument(template != null, "Cannot retrieve default permission template"); - copyPermissions(dbSession, template, entityDto, projectCreatorUserId); - } - - public boolean hasDefaultTemplateWithPermissionOnProjectCreator(DbSession dbSession, ProjectDto projectDto) { - PermissionTemplateDto template = findTemplate(dbSession, projectDto); - return hasProjectCreatorPermission(dbSession, template); - } - - private boolean hasProjectCreatorPermission(DbSession dbSession, @Nullable PermissionTemplateDto template) { - return template != null && dbClient.permissionTemplateCharacteristicDao().selectByTemplateUuids(dbSession, singletonList(template.getUuid())).stream() - .anyMatch(PermissionTemplateCharacteristicDto::getWithProjectCreator); - } - - private void copyPermissions(DbSession dbSession, PermissionTemplateDto template, EntityDto entity, @Nullable String projectCreatorUserUuid) { - List usersPermissions = dbClient.permissionTemplateDao().selectUserPermissionsByTemplateId(dbSession, template.getUuid()); - Set permissionTemplateUserUuids = usersPermissions.stream().map(PermissionTemplateUserDto::getUserUuid).collect(Collectors.toSet()); - Map userIdByUuid = dbClient.userDao().selectByUuids(dbSession, permissionTemplateUserUuids).stream().collect(Collectors.toMap(UserDto::getUuid, u -> u)); - usersPermissions - .stream() - .filter(up -> permissionValidForProject(entity.isPrivate(), up.getPermission())) - .forEach(up -> { - UserPermissionDto dto = new UserPermissionDto(uuidFactory.create(), up.getPermission(), up.getUserUuid(), entity.getUuid()); - dbClient.userPermissionDao().insert(dbSession, dto, entity, userIdByUuid.get(up.getUserUuid()), template); - }); - - List groupsPermissions = dbClient.permissionTemplateDao().selectGroupPermissionsByTemplateUuid(dbSession, template.getUuid()); - groupsPermissions - .stream() - .filter(gp -> groupNameValidForProject(entity.isPrivate(), gp.getGroupName())) - .filter(gp -> permissionValidForProject(entity.isPrivate(), gp.getPermission())) - .forEach(gp -> { - String groupUuid = isAnyone(gp.getGroupName()) ? null : gp.getGroupUuid(); - String groupName = groupUuid == null ? null : dbClient.groupDao().selectByUuid(dbSession, groupUuid).getName(); - GroupPermissionDto dto = new GroupPermissionDto() - .setUuid(uuidFactory.create()) - .setGroupUuid(groupUuid) - .setGroupName(groupName) - .setRole(gp.getPermission()) - .setEntityUuid(entity.getUuid()) - .setEntityName(entity.getName()); - - dbClient.groupPermissionDao().insert(dbSession, dto, entity, template); - }); - - List characteristics = dbClient.permissionTemplateCharacteristicDao().selectByTemplateUuids(dbSession, singletonList(template.getUuid())); - if (projectCreatorUserUuid != null) { - Set permissionsForCurrentUserAlreadyInDb = usersPermissions.stream() - .filter(userPermission -> projectCreatorUserUuid.equals(userPermission.getUserUuid())) - .map(PermissionTemplateUserDto::getPermission) - .collect(java.util.stream.Collectors.toSet()); - - UserDto userDto = dbClient.userDao().selectByUuid(dbSession, projectCreatorUserUuid); - characteristics.stream() - .filter(PermissionTemplateCharacteristicDto::getWithProjectCreator) - .filter(up -> permissionValidForProject(entity.isPrivate(), up.getPermission())) - .filter(characteristic -> !permissionsForCurrentUserAlreadyInDb.contains(characteristic.getPermission())) - .forEach(c -> { - UserPermissionDto dto = new UserPermissionDto(uuidFactory.create(), c.getPermission(), userDto.getUuid(), entity.getUuid()); - dbClient.userPermissionDao().insert(dbSession, dto, entity, userDto, template); - }); - } - } - - private static boolean permissionValidForProject(boolean isPrivateEntity, String permission) { - return isPrivateEntity || !PUBLIC_PERMISSIONS.contains(permission); - } - - private static boolean groupNameValidForProject(boolean isPrivateEntity, String groupName) { - return !isPrivateEntity || !isAnyone(groupName); - } - - /** - * Return the permission template for the given component. If no template key pattern match then consider default - * template for the component qualifier. - */ - @CheckForNull - private PermissionTemplateDto findTemplate(DbSession dbSession, EntityDto entityDto) { - List allPermissionTemplates = dbClient.permissionTemplateDao().selectAll(dbSession, null); - List matchingTemplates = new ArrayList<>(); - for (PermissionTemplateDto permissionTemplateDto : allPermissionTemplates) { - String keyPattern = permissionTemplateDto.getKeyPattern(); - if (StringUtils.isNotBlank(keyPattern) && entityDto.getKey().matches(keyPattern)) { - matchingTemplates.add(permissionTemplateDto); - } - } - checkAtMostOneMatchForComponentKey(entityDto.getKey(), matchingTemplates); - if (matchingTemplates.size() == 1) { - return matchingTemplates.get(0); - } - - String qualifier = entityDto.getQualifier(); - ResolvedDefaultTemplates resolvedDefaultTemplates = defaultTemplatesResolver.resolve(dbSession); - switch (qualifier) { - case Qualifiers.PROJECT: - return dbClient.permissionTemplateDao().selectByUuid(dbSession, resolvedDefaultTemplates.getProject()); - case Qualifiers.VIEW: - String portDefaultTemplateUuid = resolvedDefaultTemplates.getPortfolio().orElseThrow( - () -> new IllegalStateException("Failed to find default template for portfolios")); - return dbClient.permissionTemplateDao().selectByUuid(dbSession, portDefaultTemplateUuid); - case Qualifiers.APP: - String appDefaultTemplateUuid = resolvedDefaultTemplates.getApplication().orElseThrow( - () -> new IllegalStateException("Failed to find default template for applications")); - return dbClient.permissionTemplateDao().selectByUuid(dbSession, appDefaultTemplateUuid); - default: - throw new IllegalArgumentException(format("Qualifier '%s' is not supported", qualifier)); - } - } - - private static void checkAtMostOneMatchForComponentKey(String componentKey, List matchingTemplates) { - if (matchingTemplates.size() > 1) { - StringBuilder templatesNames = new StringBuilder(); - for (Iterator it = matchingTemplates.iterator(); it.hasNext(); ) { - templatesNames.append("\"").append(it.next().getName()).append("\""); - if (it.hasNext()) { - templatesNames.append(", "); - } - } - throw new TemplateMatchingKeyException(MessageFormat.format( - "The \"{0}\" key matches multiple permission templates: {1}." - + " A system administrator must update these templates so that only one of them matches the key.", - componentKey, - templatesNames.toString())); - } - } - -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/PermissionUpdater.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/PermissionUpdater.java deleted file mode 100644 index db9e77e52a2..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/PermissionUpdater.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.permission; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; -import org.sonar.db.DbSession; -import org.sonar.db.entity.EntityDto; -import org.sonar.server.es.Indexers; - -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.toMap; -import static org.sonar.api.utils.Preconditions.checkState; -import static org.sonar.server.es.Indexers.EntityEvent.PERMISSION_CHANGE; - -public class PermissionUpdater { - - private final Indexers indexers; - - private final Map, GranteeTypeSpecificPermissionUpdater> specificPermissionClassToHandler; - - public PermissionUpdater(Indexers indexers, Set> permissionChangers) { - this.indexers = indexers; - specificPermissionClassToHandler = permissionChangers.stream() - .collect(toMap(GranteeTypeSpecificPermissionUpdater::getHandledClass, Function.identity())); - } - - public void apply(DbSession dbSession, Collection changes) { - checkState(changes.stream().map(PermissionChange::getProjectUuid).distinct().count() <= 1, - "Only one project per changes is supported"); - - List projectOrViewUuids = new ArrayList<>(); - Map, List> granteeUuidToPermissionChanges = changes.stream().collect(groupingBy(change -> Optional.ofNullable(change.getUuidOfGrantee()))); - granteeUuidToPermissionChanges.values().forEach(permissionChanges -> applyForSingleGrantee(dbSession, projectOrViewUuids, permissionChanges)); - - indexers.commitAndIndexOnEntityEvent(dbSession, projectOrViewUuids, PERMISSION_CHANGE); - } - - private void applyForSingleGrantee(DbSession dbSession, List projectOrViewUuids, List permissionChanges) { - T anyPermissionChange = permissionChanges.iterator().next(); - EntityDto entity = anyPermissionChange.getEntity(); - String entityUuid = Optional.ofNullable(entity).map(EntityDto::getUuid).orElse(null); - GranteeTypeSpecificPermissionUpdater granteeTypeSpecificPermissionUpdater = getSpecificProjectUpdater(anyPermissionChange); - Set existingPermissions = granteeTypeSpecificPermissionUpdater.loadExistingEntityPermissions(dbSession, anyPermissionChange.getUuidOfGrantee(), entityUuid); - for (T permissionChange : permissionChanges) { - if (granteeTypeSpecificPermissionUpdater.apply(dbSession, existingPermissions, permissionChange) && permissionChange.getProjectUuid() != null) { - projectOrViewUuids.add(permissionChange.getProjectUuid()); - } - } - } - - private GranteeTypeSpecificPermissionUpdater getSpecificProjectUpdater(T anyPermissionChange) { - return specificPermissionClassToHandler.get(anyPermissionChange.getClass()); - } - -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/UserPermissionChange.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/UserPermissionChange.java deleted file mode 100644 index 013edc61c69..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/UserPermissionChange.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.permission; - -import javax.annotation.Nullable; -import org.sonar.db.entity.EntityDto; -import org.sonar.db.user.UserId; -import org.sonar.server.common.permission.Operation; - -import static java.util.Objects.requireNonNull; - -public class UserPermissionChange extends PermissionChange { - - private final UserId userId; - - public UserPermissionChange(Operation operation, String permission, @Nullable EntityDto entity, UserId userId, - PermissionService permissionService) { - super(operation, permission, entity, permissionService); - this.userId = requireNonNull(userId); - } - - public UserId getUserId() { - return userId; - } - - @Override - public String getUuidOfGrantee() { - return userId.getUuid(); - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/UserPermissionChanger.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/UserPermissionChanger.java deleted file mode 100644 index 0df91b7dd71..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/UserPermissionChanger.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.permission; - -import java.util.HashSet; -import java.util.Set; -import org.jetbrains.annotations.Nullable; -import org.sonar.api.web.UserRole; -import org.sonar.core.util.UuidFactory; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.entity.EntityDto; -import org.sonar.db.permission.GlobalPermission; -import org.sonar.db.permission.UserPermissionDto; - -import static org.sonar.server.exceptions.BadRequestException.checkRequest; -import static org.sonar.server.common.permission.Operation.ADD; -import static org.sonar.server.common.permission.Operation.REMOVE; - -/** - * Adds and removes user permissions. Both global and project scopes are supported. - */ -public class UserPermissionChanger implements GranteeTypeSpecificPermissionUpdater { - - private final DbClient dbClient; - private final UuidFactory uuidFactory; - - public UserPermissionChanger(DbClient dbClient, UuidFactory uuidFactory) { - this.dbClient = dbClient; - this.uuidFactory = uuidFactory; - } - - @Override - public Class getHandledClass() { - return UserPermissionChange.class; - } - - @Override - public Set loadExistingEntityPermissions(DbSession dbSession, String uuidOfGrantee, @Nullable String entityUuid) { - if (entityUuid != null) { - return new HashSet<>(dbClient.userPermissionDao().selectEntityPermissionsOfUser(dbSession, uuidOfGrantee, entityUuid)); - } - return new HashSet<>(dbClient.userPermissionDao().selectGlobalPermissionsOfUser(dbSession, uuidOfGrantee)); - } - - @Override - public boolean apply(DbSession dbSession, Set existingPermissions, UserPermissionChange change) { - ensureConsistencyWithVisibility(change); - if (isImplicitlyAlreadyDone(change)) { - return false; - } - switch (change.getOperation()) { - case ADD: - return addPermission(dbSession, existingPermissions, change); - case REMOVE: - return removePermission(dbSession, existingPermissions, change); - default: - throw new UnsupportedOperationException("Unsupported permission change: " + change.getOperation()); - } - } - - private static boolean isImplicitlyAlreadyDone(UserPermissionChange change) { - EntityDto project = change.getEntity(); - if (project != null) { - return isImplicitlyAlreadyDone(project, change); - } - return false; - } - - private static boolean isImplicitlyAlreadyDone(EntityDto project, UserPermissionChange change) { - return isAttemptToAddPublicPermissionToPublicComponent(change, project); - } - - private static boolean isAttemptToAddPublicPermissionToPublicComponent(UserPermissionChange change, EntityDto project) { - return !project.isPrivate() - && change.getOperation() == ADD - && UserRole.PUBLIC_PERMISSIONS.contains(change.getPermission()); - } - - private static void ensureConsistencyWithVisibility(UserPermissionChange change) { - EntityDto project = change.getEntity(); - if (project != null) { - checkRequest(!isAttemptToRemovePublicPermissionFromPublicComponent(change, project), - "Permission %s can't be removed from a public component", change.getPermission()); - } - } - - private static boolean isAttemptToRemovePublicPermissionFromPublicComponent(UserPermissionChange change, EntityDto entity) { - return !entity.isPrivate() - && change.getOperation() == REMOVE - && UserRole.PUBLIC_PERMISSIONS.contains(change.getPermission()); - } - - private boolean addPermission(DbSession dbSession, Set existingPermissions, UserPermissionChange change) { - if (existingPermissions.contains(change.getPermission())) { - return false; - } - UserPermissionDto dto = new UserPermissionDto(uuidFactory.create(), change.getPermission(), change.getUserId().getUuid(), - change.getProjectUuid()); - dbClient.userPermissionDao().insert(dbSession, dto, change.getEntity(), change.getUserId(), null); - return true; - } - - private boolean removePermission(DbSession dbSession, Set existingPermissions, UserPermissionChange change) { - if (!existingPermissions.contains(change.getPermission())) { - return false; - } - checkOtherAdminsExist(dbSession, change); - EntityDto entity = change.getEntity(); - if (entity != null) { - dbClient.userPermissionDao().deleteEntityPermission(dbSession, change.getUserId(), change.getPermission(), entity); - } else { - dbClient.userPermissionDao().deleteGlobalPermission(dbSession, change.getUserId(), change.getPermission()); - } - return true; - } - - private void checkOtherAdminsExist(DbSession dbSession, UserPermissionChange change) { - if (GlobalPermission.ADMINISTER.getKey().equals(change.getPermission()) && change.getProjectUuid() == null) { - int remaining = dbClient.authorizationDao().countUsersWithGlobalPermissionExcludingUserPermission(dbSession, change.getPermission(), change.getUserId().getUuid()); - checkRequest(remaining > 0, "Last user with permission '%s'. Permission cannot be removed.", GlobalPermission.ADMINISTER.getKey()); - } - } - -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/AddGroupAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/AddGroupAction.java index aef48dd5404..43d07ffe23a 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/AddGroupAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/AddGroupAction.java @@ -29,10 +29,10 @@ import org.sonar.db.DbSession; import org.sonar.db.entity.EntityDto; import org.sonar.db.user.GroupDto; import org.sonar.server.common.management.ManagedInstanceChecker; -import org.sonar.server.permission.GroupPermissionChange; +import org.sonar.server.common.permission.GroupPermissionChange; import org.sonar.server.common.permission.Operation; import org.sonar.server.permission.PermissionService; -import org.sonar.server.permission.PermissionUpdater; +import org.sonar.server.common.permission.PermissionUpdater; import org.sonar.server.user.UserSession; import static org.sonar.server.permission.ws.WsParameters.createGroupNameParameter; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/AddUserAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/AddUserAction.java index ea06411099d..d2d8203a986 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/AddUserAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/AddUserAction.java @@ -30,8 +30,8 @@ import org.sonar.db.user.UserId; import org.sonar.server.common.management.ManagedInstanceChecker; import org.sonar.server.common.permission.Operation; import org.sonar.server.permission.PermissionService; -import org.sonar.server.permission.PermissionUpdater; -import org.sonar.server.permission.UserPermissionChange; +import org.sonar.server.common.permission.PermissionUpdater; +import org.sonar.server.common.permission.UserPermissionChange; import org.sonar.server.user.UserSession; import static java.util.Collections.singletonList; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/RemoveGroupAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/RemoveGroupAction.java index 1cf108198bc..80933e06d99 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/RemoveGroupAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/RemoveGroupAction.java @@ -28,11 +28,11 @@ import org.sonar.db.DbSession; import org.sonar.db.entity.EntityDto; import org.sonar.db.user.GroupDto; import org.sonar.server.common.management.ManagedInstanceChecker; -import org.sonar.server.permission.GroupPermissionChange; +import org.sonar.server.common.permission.GroupPermissionChange; import org.sonar.server.permission.GroupUuidOrAnyone; import org.sonar.server.common.permission.Operation; import org.sonar.server.permission.PermissionService; -import org.sonar.server.permission.PermissionUpdater; +import org.sonar.server.common.permission.PermissionUpdater; import org.sonar.server.user.UserSession; import static java.util.Collections.singletonList; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/RemoveUserAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/RemoveUserAction.java index b902a82d6f2..6ec116a2d3d 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/RemoveUserAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/RemoveUserAction.java @@ -29,8 +29,8 @@ import org.sonar.db.user.UserId; import org.sonar.server.common.management.ManagedInstanceChecker; import org.sonar.server.common.permission.Operation; import org.sonar.server.permission.PermissionService; -import org.sonar.server.permission.PermissionUpdater; -import org.sonar.server.permission.UserPermissionChange; +import org.sonar.server.common.permission.PermissionUpdater; +import org.sonar.server.common.permission.UserPermissionChange; import org.sonar.server.user.UserSession; import static java.util.Collections.singletonList; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/ApplyTemplateAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/ApplyTemplateAction.java index 58503cd8ee8..dcd4325dc70 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/ApplyTemplateAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/ApplyTemplateAction.java @@ -33,7 +33,7 @@ import org.sonar.db.entity.EntityDto; import org.sonar.db.permission.template.PermissionTemplateDto; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.common.management.ManagedInstanceChecker; -import org.sonar.server.permission.PermissionTemplateService; +import org.sonar.server.common.permission.PermissionTemplateService; import org.sonar.server.permission.ws.PermissionWsSupport; import org.sonar.server.permission.ws.PermissionsWsAction; import org.sonar.server.permission.ws.ProjectWsRef; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/BulkApplyTemplateAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/BulkApplyTemplateAction.java index c5757b06995..e5270df26aa 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/BulkApplyTemplateAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/BulkApplyTemplateAction.java @@ -43,7 +43,7 @@ import org.sonar.db.component.ComponentQuery; import org.sonar.db.entity.EntityDto; import org.sonar.db.permission.template.PermissionTemplateDto; import org.sonar.server.management.ManagedProjectService; -import org.sonar.server.permission.PermissionTemplateService; +import org.sonar.server.common.permission.PermissionTemplateService; import org.sonar.server.permission.ws.PermissionWsSupport; import org.sonar.server.permission.ws.PermissionsWsAction; import org.sonar.server.permission.ws.WsParameters; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/DeleteTemplateAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/DeleteTemplateAction.java index 270aeb6268b..d60696baa9c 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/DeleteTemplateAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/DeleteTemplateAction.java @@ -27,8 +27,8 @@ import org.sonar.api.server.ws.WebService; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.permission.template.PermissionTemplateDto; -import org.sonar.server.permission.DefaultTemplatesResolver; -import org.sonar.server.permission.DefaultTemplatesResolver.ResolvedDefaultTemplates; +import org.sonar.server.common.permission.DefaultTemplatesResolver; +import org.sonar.server.common.permission.DefaultTemplatesResolver.ResolvedDefaultTemplates; import org.sonar.server.permission.ws.PermissionWsSupport; import org.sonar.server.permission.ws.PermissionsWsAction; import org.sonar.server.permission.ws.WsParameters; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/SearchTemplatesAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/SearchTemplatesAction.java index 4387439169b..e71c075ccfc 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/SearchTemplatesAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/SearchTemplatesAction.java @@ -36,8 +36,8 @@ import org.sonar.db.DbSession; import org.sonar.db.permission.template.CountByTemplateAndPermissionDto; import org.sonar.db.permission.template.PermissionTemplateCharacteristicDto; import org.sonar.db.permission.template.PermissionTemplateDto; -import org.sonar.server.permission.DefaultTemplatesResolver; -import org.sonar.server.permission.DefaultTemplatesResolver.ResolvedDefaultTemplates; +import org.sonar.server.common.permission.DefaultTemplatesResolver; +import org.sonar.server.common.permission.DefaultTemplatesResolver.ResolvedDefaultTemplates; import org.sonar.server.permission.PermissionService; import org.sonar.server.permission.ws.PermissionsWsAction; import org.sonar.server.user.UserSession; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/SearchTemplatesData.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/SearchTemplatesData.java index d5f52297710..733e859ab77 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/SearchTemplatesData.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/SearchTemplatesData.java @@ -22,7 +22,7 @@ package org.sonar.server.permission.ws.template; import com.google.common.collect.Table; import java.util.List; import org.sonar.db.permission.template.PermissionTemplateDto; -import org.sonar.server.permission.DefaultTemplatesResolver.ResolvedDefaultTemplates; +import org.sonar.server.common.permission.DefaultTemplatesResolver.ResolvedDefaultTemplates; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkState; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/CreateAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/CreateAction.java index 9dde9e80e4c..d76aff13413 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/CreateAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/CreateAction.java @@ -32,10 +32,10 @@ import org.sonar.db.component.BranchDto; import org.sonar.db.entity.EntityDto; import org.sonar.db.project.ProjectDto; import org.sonar.server.component.ComponentCreationData; -import org.sonar.server.component.ComponentCreationParameters; -import org.sonar.server.component.ComponentUpdater; -import org.sonar.server.component.NewComponent; -import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; +import org.sonar.server.common.component.ComponentCreationParameters; +import org.sonar.server.common.component.ComponentUpdater; +import org.sonar.server.common.component.NewComponent; +import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; import org.sonar.server.project.DefaultBranchNameResolver; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.project.Visibility; @@ -50,10 +50,10 @@ import static org.sonar.db.component.ComponentValidator.MAX_COMPONENT_NAME_LENGT import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; import static org.sonar.db.project.CreationMethod.Category.LOCAL; import static org.sonar.db.project.CreationMethod.getCreationMethod; -import static org.sonar.server.component.NewComponent.newComponentBuilder; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; -import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; +import static org.sonar.server.common.component.NewComponent.newComponentBuilder; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; +import static org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_CREATE; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectCreator.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectCreator.java deleted file mode 100644 index 219ed696dab..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectCreator.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.project.ws; - -import javax.annotation.Nullable; -import org.sonar.api.server.ServerSide; -import org.sonar.db.DbSession; -import org.sonar.db.project.CreationMethod; -import org.sonar.server.component.ComponentCreationData; -import org.sonar.server.component.ComponentCreationParameters; -import org.sonar.server.component.ComponentUpdater; -import org.sonar.server.component.NewComponent; -import org.sonar.server.project.ProjectDefaultVisibility; -import org.sonar.server.user.UserSession; - -import static org.sonar.api.resources.Qualifiers.PROJECT; -import static org.sonar.server.component.NewComponent.newComponentBuilder; - -@ServerSide -public class ProjectCreator { - - private final UserSession userSession; - private final ProjectDefaultVisibility projectDefaultVisibility; - private final ComponentUpdater componentUpdater; - - public ProjectCreator(UserSession userSession, ProjectDefaultVisibility projectDefaultVisibility, ComponentUpdater componentUpdater) { - this.userSession = userSession; - this.projectDefaultVisibility = projectDefaultVisibility; - this.componentUpdater = componentUpdater; - } - - public ComponentCreationData createProject(DbSession dbSession, String projectKey, String projectName, @Nullable String mainBranchName, CreationMethod creationMethod, - @Nullable Boolean isPrivate, boolean isManaged) { - boolean visibility = isPrivate != null ? isPrivate : projectDefaultVisibility.get(dbSession).isPrivate(); - NewComponent projectComponent = newComponentBuilder() - .setKey(projectKey) - .setName(projectName) - .setPrivate(visibility) - .setQualifier(PROJECT) - .build(); - ComponentCreationParameters componentCreationParameters = ComponentCreationParameters.builder() - .newComponent(projectComponent) - .userLogin(userSession.getLogin()) - .userUuid(userSession.getUuid()) - .mainBranchName(mainBranchName) - .isManaged(isManaged) - .creationMethod(creationMethod) - .build(); - return componentUpdater.createWithoutCommit(dbSession, componentCreationParameters); - } - - public ComponentCreationData createProject(DbSession dbSession, String projectKey, String projectName, @Nullable String mainBranchName, CreationMethod creationMethod) { - return createProject(dbSession, projectKey, projectName, mainBranchName, creationMethod, projectDefaultVisibility.get(dbSession).isPrivate(), false); - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java index 358b9317b5b..70ac999aee8 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java @@ -20,6 +20,7 @@ package org.sonar.server.project.ws; import org.sonar.core.platform.Module; +import org.sonar.server.common.project.ProjectCreator; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.project.ProjectLifeCycleListenersImpl; diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/ProjectKeyGeneratorTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/ProjectKeyGeneratorTest.java deleted file mode 100644 index 576c9250391..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/ProjectKeyGeneratorTest.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.almintegration.ws; - -import org.apache.commons.lang3.RandomStringUtils; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.sonar.core.util.UuidFactory; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; -import static org.sonar.server.almintegration.ws.ProjectKeyGenerator.MAX_PROJECT_KEY_SIZE; -import static org.sonar.server.almintegration.ws.ProjectKeyGenerator.PROJECT_KEY_SEPARATOR; - -@RunWith(MockitoJUnitRunner.class) -public class ProjectKeyGeneratorTest { - - private static final int MAX_UUID_SIZE = 40; - private static final String UUID_STRING = RandomStringUtils.randomAlphanumeric(MAX_UUID_SIZE); - - @Mock - private UuidFactory uuidFactory; - - @InjectMocks - private ProjectKeyGenerator projectKeyGenerator; - - @Before - public void setUp() { - when(uuidFactory.create()).thenReturn(UUID_STRING); - } - - @Test - public void generateUniqueProjectKey_shortProjectName_shouldAppendUuid() { - String fullProjectName = RandomStringUtils.randomAlphanumeric(10); - - assertThat(projectKeyGenerator.generateUniqueProjectKey(fullProjectName)) - .isEqualTo(generateExpectedKeyName(fullProjectName)); - } - - @Test - public void generateUniqueProjectKey_projectNameEqualsToMaximumSize_shouldTruncateProjectNameAndPreserveUUID() { - String fullProjectName = RandomStringUtils.randomAlphanumeric(MAX_PROJECT_KEY_SIZE); - - String projectKey = projectKeyGenerator.generateUniqueProjectKey(fullProjectName); - assertThat(projectKey) - .hasSize(MAX_PROJECT_KEY_SIZE) - .isEqualTo(generateExpectedKeyName(fullProjectName.substring(fullProjectName.length() + UUID_STRING.length() + 1 - MAX_PROJECT_KEY_SIZE))); - } - - @Test - public void generateUniqueProjectKey_projectNameBiggerThanMaximumSize_shouldTruncateProjectNameAndPreserveUUID() { - String fullProjectName = RandomStringUtils.randomAlphanumeric(MAX_PROJECT_KEY_SIZE + 50); - - String projectKey = projectKeyGenerator.generateUniqueProjectKey(fullProjectName); - assertThat(projectKey) - .hasSize(MAX_PROJECT_KEY_SIZE) - .isEqualTo(generateExpectedKeyName(fullProjectName.substring(fullProjectName.length() + UUID_STRING.length() + 1 - MAX_PROJECT_KEY_SIZE))); - } - - @Test - public void generateUniqueProjectKey_projectNameContainsSlashes_shouldBeEscaped() { - String fullProjectName = "a/b/c"; - - assertThat(projectKeyGenerator.generateUniqueProjectKey(fullProjectName)) - .isEqualTo(generateExpectedKeyName(fullProjectName.replace("/", "_"))); - } - - private String generateExpectedKeyName(String truncatedProjectName) { - return truncatedProjectName + PROJECT_KEY_SEPARATOR + UUID_STRING; - } -} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/DelegatingDevOpsProjectCreatorFactoryTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/DelegatingDevOpsProjectCreatorFactoryTest.java deleted file mode 100644 index 8aa9ab23b51..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/DelegatingDevOpsProjectCreatorFactoryTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.almsettings.ws; - -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import org.junit.Test; -import org.sonar.db.DbSession; - -import static java.util.Collections.emptySet; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class DelegatingDevOpsProjectCreatorFactoryTest { - - private static final DbSession DB_SESSION = mock(); - private static final Map CHARACTERISTICS = Map.of("toto", "tata"); - - @Test - public void getDevOpsProjectDescriptor_whenNoDelegates_shouldReturnEmptyOptional() { - DelegatingDevOpsProjectCreatorFactory noDelegates = new DelegatingDevOpsProjectCreatorFactory(emptySet()); - Optional devOpsProjectCreator = noDelegates.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS); - assertThat(devOpsProjectCreator).isEmpty(); - } - - @Test - public void getDevOpsProjectDescriptor_whenNoDelegatesReturningACreator_shouldReturnEmptyOptional() { - DelegatingDevOpsProjectCreatorFactory delegates = new DelegatingDevOpsProjectCreatorFactory(Set.of(mock(), mock())); - Optional devOpsProjectCreator = delegates.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS); - - assertThat(devOpsProjectCreator).isEmpty(); - } - - @Test - public void getDevOpsProjectDescriptor_whenOneDelegatesReturningACreator_shouldDelegate() { - DevOpsProjectCreatorFactory successfulDelegate = mock(); - DevOpsProjectCreator devOpsProjectCreator = mock(); - when(successfulDelegate.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS)).thenReturn(Optional.of(devOpsProjectCreator)); - DelegatingDevOpsProjectCreatorFactory delegates = new DelegatingDevOpsProjectCreatorFactory(Set.of(mock(), successfulDelegate)); - - assertThat(delegates.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS)).contains(devOpsProjectCreator); - } - -} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactoryTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactoryTest.java deleted file mode 100644 index f1d153ab38e..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactoryTest.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.almsettings.ws; - -import java.util.List; -import java.util.Map; -import java.util.Optional; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Answers; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.sonar.alm.client.github.GithubGlobalSettingsValidator; -import org.sonar.alm.client.github.GithubPermissionConverter; -import org.sonar.auth.github.AppInstallationToken; -import org.sonar.auth.github.GitHubSettings; -import org.sonar.auth.github.client.GithubApplicationClient; -import org.sonar.auth.github.security.AccessToken; -import org.sonar.auth.github.security.UserAccessToken; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.alm.pat.AlmPatDto; -import org.sonar.db.alm.setting.ALM; -import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.server.almintegration.ws.ProjectKeyGenerator; -import org.sonar.server.exceptions.BadConfigurationException; -import org.sonar.server.management.ManagedProjectService; -import org.sonar.server.permission.PermissionService; -import org.sonar.server.permission.PermissionUpdater; -import org.sonar.server.permission.UserPermissionChange; -import org.sonar.server.project.ProjectDefaultVisibility; -import org.sonar.server.project.ws.ProjectCreator; -import org.sonar.server.user.UserSession; - -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.core.ce.CeTaskCharacteristics.DEVOPS_PLATFORM_PROJECT_IDENTIFIER; -import static org.sonar.core.ce.CeTaskCharacteristics.DEVOPS_PLATFORM_URL; - -@RunWith(MockitoJUnitRunner.class) -public class GithubProjectCreatorFactoryTest { - private static final String PROJECT_NAME = "projectName"; - private static final String ORGANIZATION_NAME = "orgname"; - private static final String GITHUB_REPO_FULL_NAME = ORGANIZATION_NAME + "/" + PROJECT_NAME; - private static final String GITHUB_API_URL = "https://api.toto.com"; - - private static final DevOpsProjectDescriptor GITHUB_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITHUB, GITHUB_API_URL, GITHUB_REPO_FULL_NAME); - private static final Map VALID_GITHUB_PROJECT_COORDINATES = Map.of( - DEVOPS_PLATFORM_URL, GITHUB_PROJECT_DESCRIPTOR.url(), - DEVOPS_PLATFORM_PROJECT_IDENTIFIER, GITHUB_PROJECT_DESCRIPTOR.projectIdentifier()); - private static final long APP_INSTALLATION_ID = 534534534543L; - private static final String USER_ACCESS_TOKEN = "userPat"; - - @Mock - private DbSession dbSession; - @Mock - private GithubGlobalSettingsValidator githubGlobalSettingsValidator; - @Mock - private GithubApplicationClient githubApplicationClient; - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private DbClient dbClient; - @Mock - private UserSession userSession; - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private ProjectDefaultVisibility projectDefaultVisibility; - @Mock - private ProjectKeyGenerator projectKeyGenerator; - @Mock - private GitHubSettings gitHubSettings; - @Mock - private GithubPermissionConverter githubPermissionConverter; - @Mock - private AppInstallationToken appInstallationToken; - @Mock - private AppInstallationToken authAppInstallationToken; - @Mock - private PermissionService permissionService; - @Mock - private PermissionUpdater permissionUpdater; - @Mock - private ManagedProjectService managedProjectService; - @Mock - private ProjectCreator projectCreator; - - @InjectMocks - private GithubProjectCreatorFactory githubProjectCreatorFactory; - - @Test - public void getDevOpsProjectCreator_whenNoCharacteristics_shouldReturnEmpty() { - Optional devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, Map.of()); - - assertThat(devOpsProjectCreator).isEmpty(); - } - - @Test - public void getDevOpsProjectCreator_whenValidCharacteristicsButNoAlmSettingDao_shouldReturnEmpty() { - Optional devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES); - assertThat(devOpsProjectCreator).isEmpty(); - } - - @Test - public void getDevOpsProjectCreator_whenValidCharacteristicsButInvalidAlmSettingDto_shouldThrow() { - AlmSettingDto almSettingDto = mockAlmSettingDto(true); - IllegalArgumentException error = new IllegalArgumentException("error happened"); - when(githubGlobalSettingsValidator.validate(almSettingDto)).thenThrow(error); - - assertThatIllegalArgumentException().isThrownBy(() -> githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES)) - .isSameAs(error); - } - - @Test - public void getDevOpsProjectCreator_whenAppHasNoAccessToRepo_shouldReturnEmpty() { - mockAlmSettingDto(true); - when(githubApplicationClient.getInstallationId(any(), eq(GITHUB_REPO_FULL_NAME))).thenReturn(Optional.empty()); - - Optional devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES); - assertThat(devOpsProjectCreator).isEmpty(); - } - - @Test - public void getDevOpsProjectCreator_whenNotPossibleToGenerateToken_shouldThrow() { - AlmSettingDto almSettingDto = mockAlmSettingDto(true); - when(githubApplicationClient.getInstallationId(any(), eq(GITHUB_REPO_FULL_NAME))).thenReturn(Optional.of(APP_INSTALLATION_ID)); - when(githubGlobalSettingsValidator.validate(almSettingDto)).thenReturn(mock()); - when(githubApplicationClient.createAppInstallationToken(any(), eq(APP_INSTALLATION_ID))).thenReturn(Optional.empty()); - - assertThatIllegalStateException().isThrownBy(() -> githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES)) - .withMessage("Error while generating token for GitHub Api Url null (installation id: 534534534543)"); - } - - @Test - public void getDevOpsProjectCreator_whenOneValidAlmSetting_shouldInstantiateDevOpsProjectCreator() { - AlmSettingDto almSettingDto = mockAlmSettingDto(true); - mockSuccessfulGithubInteraction(); - - DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES).orElseThrow(); - - GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(almSettingDto, false, appInstallationToken); - assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedGithubProjectCreator); - } - - @Test - public void getDevOpsProjectCreator_whenOneValidAlmSettingAndPublicByDefaultAndAutoProvisioningEnabled_shouldInstantiateDevOpsProjectCreatorAndDefineAnAuthAppToken() { - AlmSettingDto almSettingDto = mockAlmSettingDto(true); - mockSuccessfulGithubInteraction(); - - when(projectDefaultVisibility.get(any()).isPrivate()).thenReturn(true); - mockValidGitHubSettings(); - - 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(); - - GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(almSettingDto, true, appInstallationToken); - assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedGithubProjectCreator); - } - - @Test - public void getDevOpsProjectCreator_whenOneMatchingAndOneNotMatchingAlmSetting_shouldInstantiateDevOpsProjectCreator() { - AlmSettingDto matchingAlmSettingDto = mockAlmSettingDto(true); - AlmSettingDto notMatchingAlmSettingDto = mockAlmSettingDto(false); - when(dbClient.almSettingDao().selectByAlm(dbSession, ALM.GITHUB)).thenReturn(List.of(notMatchingAlmSettingDto, matchingAlmSettingDto)); - - mockSuccessfulGithubInteraction(); - - DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES).orElseThrow(); - - GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(matchingAlmSettingDto, false, appInstallationToken); - assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedGithubProjectCreator); - } - - @Test - public void getDevOpsProjectCreatorFromImport_shouldInstantiateDevOpsProjectCreator() { - AlmSettingDto mockAlmSettingDto = mockAlmSettingDto(true); - mockAlmPatDto(mockAlmSettingDto); - - mockSuccessfulGithubInteraction(); - - DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(mockAlmSettingDto, GITHUB_PROJECT_DESCRIPTOR).orElseThrow(); - - GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(mockAlmSettingDto, false, new UserAccessToken(USER_ACCESS_TOKEN)); - assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedGithubProjectCreator); - } - - @Test - public void getDevOpsProjectCreatorFromImport_whenGitHubConfigDoesNotAllowAccessToRepo_shouldThrow() { - AlmSettingDto mockAlmSettingDto = mockAlmSettingDto(false); - mockAlmPatDto(mockAlmSettingDto); - - mockValidGitHubSettings(); - - when(githubApplicationClient.getInstallationId(any(), eq(GITHUB_REPO_FULL_NAME))).thenReturn(Optional.empty()); - - assertThatThrownBy(() -> githubProjectCreatorFactory.getDevOpsProjectCreator(mockAlmSettingDto, GITHUB_PROJECT_DESCRIPTOR)) - .isInstanceOf(BadConfigurationException.class) - .hasMessage(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 created.", - GITHUB_REPO_FULL_NAME)); - } - - private void mockValidGitHubSettings() { - when(gitHubSettings.appId()).thenReturn("4324"); - when(gitHubSettings.privateKey()).thenReturn("privateKey"); - when(gitHubSettings.apiURL()).thenReturn(GITHUB_API_URL); - when(gitHubSettings.isProvisioningEnabled()).thenReturn(true); - } - - 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 isInstanceManaged, AccessToken accessToken) { - DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, almSettingDto.getUrl(), GITHUB_REPO_FULL_NAME); - AppInstallationToken authAppInstallToken = isInstanceManaged ? authAppInstallationToken : null; - GithubProjectCreationParameters githubProjectCreationParameters = new GithubProjectCreationParameters(devOpsProjectDescriptor, almSettingDto, userSession, accessToken, - authAppInstallToken); - return new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, permissionUpdater, permissionService, - managedProjectService, projectCreator, githubProjectCreationParameters, gitHubSettings); - } - - private AlmSettingDto mockAlmSettingDto(boolean repoAccess) { - AlmSettingDto almSettingDto = mock(); - when(almSettingDto.getUrl()).thenReturn(repoAccess ? GITHUB_PROJECT_DESCRIPTOR.url() : "anotherUrl"); - when(almSettingDto.getAlm()).thenReturn(ALM.GITHUB); - - when(dbClient.almSettingDao().selectByAlm(dbSession, ALM.GITHUB)).thenReturn(List.of(almSettingDto)); - return almSettingDto; - } - - private void mockAlmPatDto(AlmSettingDto almSettingDto) { - when(userSession.getUuid()).thenReturn("userUuid"); - when(dbClient.almPatDao().selectByUserAndAlmSetting(any(), eq("userUuid"), eq(almSettingDto))) - .thenReturn(Optional.of(new AlmPatDto().setPersonalAccessToken(USER_ACCESS_TOKEN))); - } - -} 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 deleted file mode 100644 index 619846492bb..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorTest.java +++ /dev/null @@ -1,477 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.almsettings.ws; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Optional; -import java.util.Set; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Answers; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.sonar.alm.client.github.GithubPermissionConverter; -import org.sonar.api.resources.Qualifiers; -import org.sonar.api.web.UserRole; -import org.sonar.auth.github.AppInstallationToken; -import org.sonar.auth.github.GitHubSettings; -import org.sonar.auth.github.GsonRepositoryCollaborator; -import org.sonar.auth.github.GsonRepositoryPermissions; -import org.sonar.auth.github.GsonRepositoryTeam; -import org.sonar.auth.github.client.GithubApplicationClient; -import org.sonar.auth.github.security.AccessToken; -import org.sonar.db.DbClient; -import org.sonar.db.alm.setting.ALM; -import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.db.alm.setting.ProjectAlmSettingDao; -import org.sonar.db.alm.setting.ProjectAlmSettingDto; -import org.sonar.db.component.BranchDto; -import org.sonar.db.project.CreationMethod; -import org.sonar.db.project.ProjectDto; -import org.sonar.db.provisioning.GithubPermissionsMappingDto; -import org.sonar.db.user.GroupDto; -import org.sonar.server.almintegration.ws.ProjectKeyGenerator; -import org.sonar.server.component.ComponentCreationData; -import org.sonar.server.component.ComponentCreationParameters; -import org.sonar.server.component.ComponentUpdater; -import org.sonar.server.component.NewComponent; -import org.sonar.server.management.ManagedProjectService; -import org.sonar.server.permission.PermissionService; -import org.sonar.server.permission.PermissionServiceImpl; -import org.sonar.server.permission.PermissionUpdater; -import org.sonar.server.permission.UserPermissionChange; -import org.sonar.server.project.ProjectDefaultVisibility; -import org.sonar.server.project.Visibility; -import org.sonar.server.project.ws.ProjectCreator; -import org.sonar.server.user.UserSession; - -import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.toSet; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.lenient; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.sonar.db.project.CreationMethod.ALM_IMPORT_API; -import static org.sonar.db.project.CreationMethod.SCANNER_API_DEVOPS_AUTO_CONFIG; - -@ExtendWith(MockitoExtension.class) -class GithubProjectCreatorTest { - - private static final String ORGANIZATION_NAME = "orga2"; - private static final String REPOSITORY_NAME = "repo1"; - - private static final String MAIN_BRANCH_NAME = "defaultBranch"; - private static final DevOpsProjectDescriptor DEVOPS_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITHUB, "http://api.com", ORGANIZATION_NAME + "/" + REPOSITORY_NAME); - private static final String ALM_SETTING_KEY = "github_config_1"; - private static final String USER_LOGIN = "userLogin"; - private static final String USER_UUID = "userUuid"; - - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private DbClient dbClient; - @Mock - private GithubApplicationClient githubApplicationClient; - @Mock - private GithubPermissionConverter githubPermissionConverter; - @Mock - private ProjectKeyGenerator projectKeyGenerator; - @Mock - private ComponentUpdater componentUpdater; - @Mock - private GithubProjectCreationParameters githubProjectCreationParameters; - @Mock - private AccessToken devOpsAppInstallationToken; - @Mock - private AppInstallationToken authAppInstallationToken; - @Mock - private UserSession userSession; - @Mock - private AlmSettingDto almSettingDto; - private final PermissionService permissionService = new PermissionServiceImpl(mock()); - @Mock - private PermissionUpdater permissionUpdater; - @Mock - private ManagedProjectService managedProjectService; - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private ProjectDefaultVisibility projectDefaultVisibility; - private final GitHubSettings gitHubSettings = mock(); - - private GithubProjectCreator githubProjectCreator; - - @Captor - ArgumentCaptor componentCreationParametersCaptor; - @Captor - ArgumentCaptor projectAlmSettingDtoCaptor; - - @BeforeEach - void setup() { - lenient().when(userSession.getLogin()).thenReturn(USER_LOGIN); - lenient().when(userSession.getUuid()).thenReturn(USER_UUID); - - lenient().when(almSettingDto.getUrl()).thenReturn(DEVOPS_PROJECT_DESCRIPTOR.url()); - lenient().when(almSettingDto.getKey()).thenReturn(ALM_SETTING_KEY); - - when(githubProjectCreationParameters.devOpsProjectDescriptor()).thenReturn(DEVOPS_PROJECT_DESCRIPTOR); - when(githubProjectCreationParameters.userSession()).thenReturn(userSession); - when(githubProjectCreationParameters.devOpsAppInstallationToken()).thenReturn(devOpsAppInstallationToken); - when(githubProjectCreationParameters.authAppInstallationToken()).thenReturn(authAppInstallationToken); - when(githubProjectCreationParameters.almSettingDto()).thenReturn(almSettingDto); - - ProjectCreator projectCreator = new ProjectCreator(userSession, projectDefaultVisibility, componentUpdater); - githubProjectCreator = new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, - permissionUpdater, permissionService, managedProjectService, projectCreator, githubProjectCreationParameters, gitHubSettings); - - } - - @Test - 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 - void isScanAllowedUsingPermissionsFromDevopsPlatform_whenUserIsNotAGitHubUser_returnsFalse() { - assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isFalse(); - } - - @Test - void isScanAllowedUsingPermissionsFromDevopsPlatform_whenCollaboratorHasDirectAccessButNoScanPermissions_returnsFalse() { - GsonRepositoryCollaborator collaborator1 = mockCollaborator("collaborator1", 1, "role1", "read", "admin"); - mockGithubCollaboratorsFromApi(collaborator1); - bindSessionToCollaborator(collaborator1); - - assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isFalse(); - } - - @Test - void isScanAllowedUsingPermissionsFromDevopsPlatform_whenCollaboratorHasDirectAccess_returnsTrue() { - GsonRepositoryCollaborator collaborator1 = mockCollaborator("collaborator1", 1, "role1", "read", "admin"); - GsonRepositoryCollaborator collaborator2 = mockCollaborator("collaborator2", 2, "role2", "read", "scan"); - mockGithubCollaboratorsFromApi(collaborator1, collaborator2); - bindSessionToCollaborator(collaborator2); - - assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isTrue(); - } - - @Test - void isScanAllowedUsingPermissionsFromDevopsPlatform_whenAccessViaTeamButNoScanPermissions_returnsFalse() { - GsonRepositoryTeam team2 = mockGithubTeam("team2", 2, "role2", "another_perm", UserRole.ADMIN); - mockTeamsFromApi(team2); - bindGroupsToUser(team2.name()); - - assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isFalse(); - } - - @Test - void isScanAllowedUsingPermissionsFromDevopsPlatform_whenAccessViaTeam_returnsTrue() { - GsonRepositoryTeam team1 = mockGithubTeam("team1", 1, "role1", "read", "another_perm"); - GsonRepositoryTeam team2 = mockGithubTeam("team2", 2, "role2", "another_perm", UserRole.SCAN); - mockTeamsFromApi(team1, team2); - bindGroupsToUser(team1.name(), team2.name()); - - assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isTrue(); - } - - @Test - void isScanAllowedUsingPermissionsFromDevopsPlatform_whenAccessViaTeamButUserNotInTeam_returnsFalse() { - GsonRepositoryTeam team1 = mockGithubTeam("team1", 1, "role1", "read", "another_perm"); - GsonRepositoryTeam team2 = mockGithubTeam("team2", 2, "role2", "another_perm", UserRole.SCAN); - mockTeamsFromApi(team1, team2); - bindGroupsToUser(team1.name()); - - assertThat(githubProjectCreator.isScanAllowedUsingPermissionsFromDevopsPlatform()).isFalse(); - } - - private void bindSessionToCollaborator(GsonRepositoryCollaborator collaborator1) { - UserSession.ExternalIdentity externalIdentity = new UserSession.ExternalIdentity(String.valueOf(collaborator1.id()), collaborator1.name()); - when(userSession.getExternalIdentity()).thenReturn(Optional.of(externalIdentity)); - } - - private GsonRepositoryCollaborator mockCollaborator(String collaboratorLogin, int id, String role1, String... sqPermissions) { - GsonRepositoryCollaborator collaborator = new GsonRepositoryCollaborator(collaboratorLogin, id, role1, - new GsonRepositoryPermissions(false, false, false, false, false)); - mockPermissionsConversion(collaborator, sqPermissions); - return collaborator; - } - - private void mockGithubCollaboratorsFromApi(GsonRepositoryCollaborator... repositoryCollaborators) { - Set collaborators = Arrays.stream(repositoryCollaborators).collect(toSet()); - 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) { - GsonRepositoryTeam gsonRepositoryTeam = new GsonRepositoryTeam(name, id, name + "slug", role, new GsonRepositoryPermissions(false, false, false, false, false)); - mockPermissionsConversion(gsonRepositoryTeam, sqPermissions); - return gsonRepositoryTeam; - } - - private void mockTeamsFromApi(GsonRepositoryTeam... repositoryTeams) { - when(githubApplicationClient.getRepositoryTeams(DEVOPS_PROJECT_DESCRIPTOR.url(), authAppInstallationToken, ORGANIZATION_NAME, REPOSITORY_NAME)) - .thenReturn(Arrays.stream(repositoryTeams).collect(toSet())); - } - - private void mockPermissionsConversion(GsonRepositoryCollaborator collaborator, String... sqPermissions) { - Set githubPermissionsMappingDtos = mockPermissionsMappingsDtos(); - lenient().when(githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(githubPermissionsMappingDtos, collaborator.roleName(), collaborator.permissions())) - .thenReturn(Arrays.stream(sqPermissions).collect(toSet())); - } - - private void mockPermissionsConversion(GsonRepositoryTeam team, String... sqPermissions) { - Set githubPermissionsMappingDtos = mockPermissionsMappingsDtos(); - lenient().when(githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(githubPermissionsMappingDtos, team.permission(), team.permissions())) - .thenReturn(Arrays.stream(sqPermissions).collect(toSet())); - } - - private Set mockPermissionsMappingsDtos() { - Set githubPermissionsMappingDtos = Set.of(mock(GithubPermissionsMappingDto.class)); - when(dbClient.githubPermissionsMappingDao().findAll(any())).thenReturn(githubPermissionsMappingDtos); - return githubPermissionsMappingDtos; - } - - private void bindGroupsToUser(String... groupNames) { - Set groupDtos = Arrays.stream(groupNames) - .map(groupName -> new GroupDto().setName(ORGANIZATION_NAME + "/" + groupName).setUuid("uuid_" + groupName)) - .collect(toSet()); - when(userSession.getGroups()).thenReturn(groupDtos); - } - - @Test - void createProjectAndBindToDevOpsPlatform_whenRepoNotFound_throws() { - assertThatIllegalStateException().isThrownBy( - () -> githubProjectCreator.createProjectAndBindToDevOpsPlatform(mock(), SCANNER_API_DEVOPS_AUTO_CONFIG, false, null, null)) - .withMessage("Impossible to find the repository 'orga2/repo1' on GitHub, using the devops config " + ALM_SETTING_KEY); - } - - @Test - void createProjectAndBindToDevOpsPlatformFromScanner_whenRepoFoundOnGitHub_successfullyCreatesProject() { - // given - mockGitHubRepository(); - - ComponentCreationData componentCreationData = mockProjectCreation("generated_orga2/repo1"); - ProjectAlmSettingDao projectAlmSettingDao = mock(); - when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); - when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE); - - // when - ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), - SCANNER_API_DEVOPS_AUTO_CONFIG, false, null, null); - - // then - assertThat(actualComponentCreationData).isEqualTo(componentCreationData); - - ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); - assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters, "generated_orga2/repo1", SCANNER_API_DEVOPS_AUTO_CONFIG); - assertThat(componentCreationParameters.isManaged()).isFalse(); - assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue(); - - verify(projectAlmSettingDao).insertOrUpdate(any(), projectAlmSettingDtoCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq("generated_orga2/repo1")); - ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue(); - assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto); - } - - @Test - void createProjectAndBindToDevOpsPlatformFromScanner_whenRepoFoundOnGitHubAndVisibilitySynchronizationEnabled_successfullyCreatesProjectAndSetsVisibility() { - // given - mockPublicGithubRepository(); - - ComponentCreationData componentCreationData = mockProjectCreation("generated_orga2/repo1"); - ProjectAlmSettingDao projectAlmSettingDao = mock(); - when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); - when(gitHubSettings.isProvisioningEnabled()).thenReturn(true); - when(gitHubSettings.isProjectVisibilitySynchronizationActivated()).thenReturn(true); - - // when - ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), - SCANNER_API_DEVOPS_AUTO_CONFIG, false, null, null); - - // then - assertThat(actualComponentCreationData).isEqualTo(componentCreationData); - - ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); - assertThat(componentCreationParameters.newComponent().isPrivate()).isFalse(); - } - - @Test - void createProjectAndBindToDevOpsPlatformFromScanner_whenRepoFoundOnGitHubAndVisibilitySynchronizationDisabled_successfullyCreatesProjectAndMakesProjectPrivate() { - // given - mockGitHubRepository(); - - ComponentCreationData componentCreationData = mockProjectCreation("generated_orga2/repo1"); - ProjectAlmSettingDao projectAlmSettingDao = mock(); - when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); - when(gitHubSettings.isProvisioningEnabled()).thenReturn(true); - when(gitHubSettings.isProjectVisibilitySynchronizationActivated()).thenReturn(false); - - // when - ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), - SCANNER_API_DEVOPS_AUTO_CONFIG, false, null, null); - - // then - assertThat(actualComponentCreationData).isEqualTo(componentCreationData); - - ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); - assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue(); - } - - @Test - void createProjectAndBindToDevOpsPlatformFromApi_whenRepoFoundOnGitHub_successfullyCreatesProject() { - // given - String projectKey = "customProjectKey"; - mockGitHubRepository(); - - ComponentCreationData componentCreationData = mockProjectCreation(projectKey); - ProjectAlmSettingDao projectAlmSettingDao = mock(); - when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); - when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE); - - // when - ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), ALM_IMPORT_API, false, projectKey, - null); - - // then - assertThat(actualComponentCreationData).isEqualTo(componentCreationData); - - ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); - assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters, projectKey, ALM_IMPORT_API); - assertThat(componentCreationParameters.isManaged()).isFalse(); - assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue(); - - verify(projectAlmSettingDao).insertOrUpdate(any(), projectAlmSettingDtoCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq(projectKey)); - ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue(); - assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto); - } - - @Captor - private ArgumentCaptor> permissionChangesCaptor; - - @Test - void createProjectAndBindToDevOpsPlatformFromApi_whenRepoFoundOnGitHubAutoProvisioningOnAndRepoPrivate_successfullyCreatesProject() { - // given - String projectKey = "customProjectKey"; - mockGitHubRepository(); - - ComponentCreationData componentCreationData = mockProjectCreation(projectKey); - ProjectAlmSettingDao projectAlmSettingDao = mock(); - when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); - when(gitHubSettings.isProvisioningEnabled()).thenReturn(true); - - // when - ComponentCreationData actualComponentCreationData = githubProjectCreator.createProjectAndBindToDevOpsPlatform(dbClient.openSession(true), ALM_IMPORT_API, false, projectKey, - null); - - // then - assertThat(actualComponentCreationData).isEqualTo(componentCreationData); - - ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); - assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters, projectKey, ALM_IMPORT_API); - assertThat(componentCreationParameters.isManaged()).isTrue(); - assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue(); - - verifyScanPermissionWasAddedToUser(actualComponentCreationData); - verifyProjectSyncTaskWasCreated(actualComponentCreationData); - - verify(projectAlmSettingDao).insertOrUpdate(any(), projectAlmSettingDtoCaptor.capture(), eq(ALM_SETTING_KEY), eq(REPOSITORY_NAME), eq(projectKey)); - ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue(); - assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto); - } - - private void verifyProjectSyncTaskWasCreated(ComponentCreationData componentCreationData) { - String projectUuid = requireNonNull(componentCreationData.projectDto()).getUuid(); - String mainBranchUuid = requireNonNull(componentCreationData.mainBranchDto()).getUuid(); - verify(managedProjectService).queuePermissionSyncTask(USER_UUID, mainBranchUuid, projectUuid); - } - - private void verifyScanPermissionWasAddedToUser(ComponentCreationData actualComponentCreationData) { - verify(permissionUpdater).apply(any(), permissionChangesCaptor.capture()); - UserPermissionChange permissionChange = permissionChangesCaptor.getValue().iterator().next(); - assertThat(permissionChange.getUserId().getUuid()).isEqualTo(userSession.getUuid()); - assertThat(permissionChange.getUserId().getLogin()).isEqualTo(userSession.getLogin()); - assertThat(permissionChange.getPermission()).isEqualTo(UserRole.SCAN); - assertThat(permissionChange.getProjectUuid()).isEqualTo(actualComponentCreationData.projectDto().getUuid()); - } - - private void mockPublicGithubRepository() { - GithubApplicationClient.Repository repository = mockGitHubRepository(); - when(repository.isPrivate()).thenReturn(false); - } - - private GithubApplicationClient.Repository mockGitHubRepository() { - GithubApplicationClient.Repository repository = mock(); - when(repository.getDefaultBranch()).thenReturn(MAIN_BRANCH_NAME); - when(repository.getName()).thenReturn(REPOSITORY_NAME); - when(repository.getFullName()).thenReturn(DEVOPS_PROJECT_DESCRIPTOR.projectIdentifier()); - lenient().when(repository.isPrivate()).thenReturn(true); - 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()); - return repository; - } - - private ComponentCreationData mockProjectCreation(String projectKey) { - ComponentCreationData componentCreationData = mock(); - ProjectDto projectDto = mockProjectDto(projectKey); - when(componentCreationData.projectDto()).thenReturn(projectDto); - BranchDto branchDto = mock(); - when(componentCreationData.mainBranchDto()).thenReturn(branchDto); - when(componentUpdater.createWithoutCommit(any(), componentCreationParametersCaptor.capture())).thenReturn(componentCreationData); - return componentCreationData; - } - - private static ProjectDto mockProjectDto(String projectKey) { - ProjectDto projectDto = mock(); - when(projectDto.getName()).thenReturn(REPOSITORY_NAME); - when(projectDto.getKey()).thenReturn(projectKey); - when(projectDto.getUuid()).thenReturn("project-uuid-1"); - return projectDto; - } - - private static void assertComponentCreationParametersContainsCorrectInformation(ComponentCreationParameters componentCreationParameters, String expectedKey, - CreationMethod expectedCreationMethod) { - assertThat(componentCreationParameters.creationMethod()).isEqualTo(expectedCreationMethod); - assertThat(componentCreationParameters.mainBranchName()).isEqualTo(MAIN_BRANCH_NAME); - assertThat(componentCreationParameters.userLogin()).isEqualTo(USER_LOGIN); - assertThat(componentCreationParameters.userUuid()).isEqualTo(USER_UUID); - - NewComponent newComponent = componentCreationParameters.newComponent(); - assertThat(newComponent.isProject()).isTrue(); - assertThat(newComponent.qualifier()).isEqualTo(Qualifiers.PROJECT); - assertThat(newComponent.key()).isEqualTo(expectedKey); - assertThat(newComponent.name()).isEqualTo(REPOSITORY_NAME); - } - - private static void assertAlmSettingsDtoContainsCorrectInformation(AlmSettingDto almSettingDto, ProjectDto projectDto, ProjectAlmSettingDto projectAlmSettingDto) { - assertThat(projectAlmSettingDto.getAlmRepo()).isEqualTo(DEVOPS_PROJECT_DESCRIPTOR.projectIdentifier()); - assertThat(projectAlmSettingDto.getAlmSlug()).isNull(); - assertThat(projectAlmSettingDto.getAlmSettingUuid()).isEqualTo(almSettingDto.getUuid()); - assertThat(projectAlmSettingDto.getProjectUuid()).isEqualTo(projectDto.getUuid()); - assertThat(projectAlmSettingDto.getMonorepo()).isFalse(); - assertThat(projectAlmSettingDto.getSummaryCommentEnabled()).isTrue(); - } -} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/gitlab/GitlabProjectCreatorFactoryTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/gitlab/GitlabProjectCreatorFactoryTest.java deleted file mode 100644 index 6df2d2c6e4c..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/gitlab/GitlabProjectCreatorFactoryTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.almsettings.ws.gitlab; - -import java.util.Map; -import org.assertj.core.api.AssertionsForClassTypes; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.sonar.db.DbSession; -import org.sonar.db.alm.setting.ALM; -import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.server.almsettings.ws.DevOpsProjectDescriptor; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class GitlabProjectCreatorFactoryTest { - - @InjectMocks - private GitlabProjectCreatorFactory underTest; - - - @Test - void getDevOpsProjectCreator_withCharacteristics_returnsEmpty() { - assertThat(underTest.getDevOpsProjectCreator(mock(DbSession.class), Map.of())).isEmpty(); - } - - - @Test - void getDevOpsProjectCreator_whenDevOpsPlatformIsNotGitlab_returnsEmpty() { - AlmSettingDto almSetting = mock(); - when(almSetting.getAlm()).thenReturn(ALM.AZURE_DEVOPS); - AssertionsForClassTypes.assertThat(underTest.getDevOpsProjectCreator(almSetting, Mockito.mock(DevOpsProjectDescriptor.class))).isEmpty(); - } - - - @Test - void getDevOpsProjectCreator_whenDevOpsPlatformIsNotGitlab_returnsProjectCreator() { - AlmSettingDto almSetting = mock(); - when(almSetting.getAlm()).thenReturn(ALM.GITLAB); - assertThat(underTest.getDevOpsProjectCreator(almSetting, mock(DevOpsProjectDescriptor.class))).isNotEmpty(); - } - -} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/gitlab/GitlabProjectCreatorTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/gitlab/GitlabProjectCreatorTest.java deleted file mode 100644 index ad989a73288..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/gitlab/GitlabProjectCreatorTest.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.almsettings.ws.gitlab; - -import java.util.List; -import java.util.Optional; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Answers; -import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.sonar.alm.client.gitlab.GitLabBranch; -import org.sonar.alm.client.gitlab.GitlabApplicationClient; -import org.sonar.alm.client.gitlab.GitlabServerException; -import org.sonar.alm.client.gitlab.Project; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.alm.pat.AlmPatDto; -import org.sonar.db.alm.setting.ALM; -import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.db.alm.setting.ProjectAlmSettingDto; -import org.sonar.db.project.CreationMethod; -import org.sonar.db.project.ProjectDto; -import org.sonar.server.almintegration.ws.ProjectKeyGenerator; -import org.sonar.server.almsettings.ws.DevOpsProjectDescriptor; -import org.sonar.server.component.ComponentCreationData; -import org.sonar.server.project.ws.ProjectCreator; -import org.sonar.server.user.UserSession; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.lenient; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class GitlabProjectCreatorTest { - - private static final String PROJECT_UUID = "projectUuid"; - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private DbClient dbClient; - - @Mock - private ProjectKeyGenerator projectKeyGenerator; - - @Mock - private ProjectCreator projectCreator; - - @Mock - private AlmSettingDto almSettingDto; - @Mock - private DevOpsProjectDescriptor devOpsProjectDescriptor; - @Mock - private GitlabApplicationClient gitlabApplicationClient; - @Mock - private UserSession userSession; - - @InjectMocks - private GitlabProjectCreator underTest; - - private static final String USER_LOGIN = "userLogin"; - private static final String USER_UUID = "userUuid"; - - private static final String GROUP_NAME = "group1"; - private static final String REPOSITORY_PATH_WITH_NAMESPACE = "pathWith/namespace"; - - private static final String GITLAB_PROJECT_NAME = "gitlabProjectName"; - - private static final String REPOSITORY_ID = "1234"; - - private static final String MAIN_BRANCH_NAME = "defaultBranch"; - - private static final String ALM_SETTING_KEY = "gitlab_config_1"; - private static final String ALM_SETTING_UUID = "almSettingUuid"; - - private static final String USER_PAT = "1234"; - - public static final String GITLAB_URL = "http://api.com"; - private static final DevOpsProjectDescriptor DEVOPS_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITLAB, GITLAB_URL, REPOSITORY_ID); - - @BeforeEach - void setup() { - lenient().when(userSession.getLogin()).thenReturn(USER_LOGIN); - lenient().when(userSession.getUuid()).thenReturn(USER_UUID); - - lenient().when(almSettingDto.getUrl()).thenReturn(GITLAB_URL); - lenient().when(almSettingDto.getKey()).thenReturn(ALM_SETTING_KEY); - lenient().when(almSettingDto.getUuid()).thenReturn(ALM_SETTING_UUID); - - lenient().when(devOpsProjectDescriptor.projectIdentifier()).thenReturn(REPOSITORY_ID); - lenient().when(devOpsProjectDescriptor.url()).thenReturn(GITLAB_URL); - lenient().when(devOpsProjectDescriptor.alm()).thenReturn(ALM.GITLAB); - } - - @Test - void isScanAllowedUsingPermissionsFromDevopsPlatform_shouldThrowUnsupportedOperationException() { - assertThatExceptionOfType(UnsupportedOperationException.class) - .isThrownBy(() -> underTest.isScanAllowedUsingPermissionsFromDevopsPlatform()) - .withMessage("Not Implemented"); - } - - @Test - void createProjectAndBindToDevOpsPlatform_whenUserHasNoPat_throws() { - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, false, null, null)) - .withMessage("personal access token for 'gitlab_config_1' is missing"); - } - - @Test - void createProjectAndBindToDevOpsPlatform_whenRepoNotFound_throws() { - mockPatForUser(); - when(gitlabApplicationClient.getProject(DEVOPS_PROJECT_DESCRIPTOR.url(), USER_PAT, Long.valueOf(REPOSITORY_ID))).thenThrow(new GitlabServerException(404, "Not found")); - assertThatExceptionOfType(IllegalStateException.class) - .isThrownBy(() -> underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, false, null, null)) - .withMessage("Failed to fetch GitLab project with ID '1234' from 'http://api.com'"); - - } - - @Test - void createProjectAndBindToDevOpsPlatform_whenRepoFoundOnGitlab_successfullyCreatesProject() { - mockPatForUser(); - mockGitlabProject(); - mockMainBranch(); - mockProjectCreation("projectKey", "projectName"); - - underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, true, "projectKey", "projectName"); - - ArgumentCaptor projectAlmSettingCaptor = ArgumentCaptor.forClass(ProjectAlmSettingDto.class); - - verify(dbClient.projectAlmSettingDao()).insertOrUpdate(any(), projectAlmSettingCaptor.capture(), eq(ALM_SETTING_KEY), eq("projectName"), eq("projectKey")); - - ProjectAlmSettingDto createdProjectAlmSettingDto = projectAlmSettingCaptor.getValue(); - - assertThat(createdProjectAlmSettingDto.getAlmSettingUuid()).isEqualTo(ALM_SETTING_UUID); - assertThat(createdProjectAlmSettingDto.getAlmRepo()).isEqualTo(REPOSITORY_ID); - assertThat(createdProjectAlmSettingDto.getProjectUuid()).isEqualTo(PROJECT_UUID); - assertThat(createdProjectAlmSettingDto.getMonorepo()).isTrue(); - - } - - @Test - void createProjectAndBindToDevOpsPlatform_whenNoKeyAndNameSpecified_generatesOneKeyAndUsersGitlabProjectName() { - mockPatForUser(); - mockGitlabProject(); - mockMainBranch(); - - String generatedProjectKey = "generatedProjectKey"; - when(projectKeyGenerator.generateUniqueProjectKey(REPOSITORY_PATH_WITH_NAMESPACE)).thenReturn(generatedProjectKey); - - mockProjectCreation(generatedProjectKey, GITLAB_PROJECT_NAME); - - underTest.createProjectAndBindToDevOpsPlatform(mock(DbSession.class), CreationMethod.ALM_IMPORT_API, true, null, null); - - ArgumentCaptor projectAlmSettingCaptor = ArgumentCaptor.forClass(ProjectAlmSettingDto.class); - - verify(dbClient.projectAlmSettingDao()).insertOrUpdate(any(), projectAlmSettingCaptor.capture(), eq(ALM_SETTING_KEY), eq(GITLAB_PROJECT_NAME), eq(generatedProjectKey)); - - ProjectAlmSettingDto createdProjectAlmSettingDto = projectAlmSettingCaptor.getValue(); - - assertThat(createdProjectAlmSettingDto.getAlmSettingUuid()).isEqualTo(ALM_SETTING_UUID); - assertThat(createdProjectAlmSettingDto.getAlmRepo()).isEqualTo(REPOSITORY_ID); - assertThat(createdProjectAlmSettingDto.getProjectUuid()).isEqualTo(PROJECT_UUID); - assertThat(createdProjectAlmSettingDto.getMonorepo()).isTrue(); - } - - private void mockPatForUser() { - AlmPatDto almPatDto = mock(); - when(almPatDto.getPersonalAccessToken()).thenReturn(USER_PAT); - when(dbClient.almPatDao().selectByUserAndAlmSetting(any(), eq(USER_UUID), eq(almSettingDto))).thenReturn(Optional.of(almPatDto)); - } - - private void mockGitlabProject() { - Project project = mock(Project.class); - lenient().when(project.getPathWithNamespace()).thenReturn(REPOSITORY_PATH_WITH_NAMESPACE); - when(project.getName()).thenReturn(GITLAB_PROJECT_NAME); - when(gitlabApplicationClient.getProject(DEVOPS_PROJECT_DESCRIPTOR.url(), USER_PAT, Long.valueOf(REPOSITORY_ID))).thenReturn(project); - - } - - private void mockMainBranch() { - when(gitlabApplicationClient.getBranches(DEVOPS_PROJECT_DESCRIPTOR.url(), USER_PAT, Long.valueOf(REPOSITORY_ID))) - .thenReturn(List.of(new GitLabBranch("notMain", false), new GitLabBranch(MAIN_BRANCH_NAME, true))); - } - - private void mockProjectCreation(String projectKey, String projectName) { - ComponentCreationData componentCreationData = mock(); - ProjectDto projectDto = mock(); - when(componentCreationData.projectDto()).thenReturn(projectDto); - when(projectDto.getUuid()).thenReturn(PROJECT_UUID); - when(projectDto.getKey()).thenReturn(projectKey); - when(projectDto.getName()).thenReturn(projectName); - when(projectCreator.createProject(any(), eq(projectKey), eq(projectName), eq(MAIN_BRANCH_NAME), eq(CreationMethod.ALM_IMPORT_API))) - .thenReturn(componentCreationData); - } - -} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/NewComponentTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/NewComponentTest.java index dd29147cdf8..4553c0bc973 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/NewComponentTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/NewComponentTest.java @@ -20,13 +20,14 @@ package org.sonar.server.component; import org.junit.Test; +import org.sonar.server.common.component.NewComponent; import static com.google.common.base.Strings.repeat; import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.sonar.api.resources.Qualifiers.PROJECT; -import static org.sonar.server.component.NewComponent.newComponentBuilder; +import static org.sonar.server.common.component.NewComponent.newComponentBuilder; public class NewComponentTest { private static final String KEY = "key"; diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/permission/ws/template/SearchTemplatesDataTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/permission/ws/template/SearchTemplatesDataTest.java index b17b456e942..621fc056d7d 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/permission/ws/template/SearchTemplatesDataTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/permission/ws/template/SearchTemplatesDataTest.java @@ -21,7 +21,7 @@ package org.sonar.server.permission.ws.template; import com.google.common.collect.HashBasedTable; import org.junit.Test; -import org.sonar.server.permission.DefaultTemplatesResolverImpl; +import org.sonar.server.common.permission.DefaultTemplatesResolverImpl; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index bdf8daafc69..a4056d43a92 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -63,13 +63,13 @@ import org.sonar.core.platform.SpringComponentContainer; import org.sonar.server.almintegration.ws.AlmIntegrationsWSModule; import org.sonar.server.almintegration.ws.CredentialsEncoderHelper; import org.sonar.server.almintegration.ws.ImportHelper; -import org.sonar.server.almintegration.ws.ProjectKeyGenerator; +import org.sonar.server.common.almintegration.ProjectKeyGenerator; import org.sonar.server.almintegration.ws.github.GithubProvisioningWs; import org.sonar.server.almsettings.MultipleAlmFeature; import org.sonar.server.almsettings.ws.AlmSettingsWsModule; -import org.sonar.server.almsettings.ws.DelegatingDevOpsProjectCreatorFactory; -import org.sonar.server.almsettings.ws.GithubProjectCreatorFactory; -import org.sonar.server.almsettings.ws.gitlab.GitlabProjectCreatorFactory; +import org.sonar.server.common.almsettings.DelegatingDevOpsProjectCreatorFactory; +import org.sonar.server.common.almsettings.github.GithubProjectCreatorFactory; +import org.sonar.server.common.almsettings.gitlab.GitlabProjectCreatorFactory; import org.sonar.server.authentication.AuthenticationModule; import org.sonar.server.authentication.DefaultAdminCredentialsVerifierImpl; import org.sonar.server.authentication.DefaultAdminCredentialsVerifierNotificationHandler; @@ -92,7 +92,7 @@ import org.sonar.server.common.text.MacroInterpreter; import org.sonar.server.component.ComponentCleanerService; import org.sonar.server.component.ComponentFinder; import org.sonar.server.component.ComponentService; -import org.sonar.server.component.ComponentUpdater; +import org.sonar.server.common.component.ComponentUpdater; import org.sonar.server.component.index.ComponentIndex; import org.sonar.server.component.index.ComponentIndexDefinition; import org.sonar.server.component.index.EntityDefinitionIndexer; @@ -159,15 +159,15 @@ import org.sonar.server.monitoring.devops.AzureMetricsTask; import org.sonar.server.monitoring.devops.BitbucketMetricsTask; import org.sonar.server.monitoring.devops.GithubMetricsTask; import org.sonar.server.monitoring.devops.GitlabMetricsTask; -import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; +import org.sonar.server.common.newcodeperiod.NewCodeDefinitionResolver; import org.sonar.server.newcodeperiod.ws.NewCodePeriodsWsModule; import org.sonar.server.notification.NotificationModule; import org.sonar.server.notification.ws.NotificationWsModule; -import org.sonar.server.permission.DefaultTemplatesResolverImpl; -import org.sonar.server.permission.GroupPermissionChanger; -import org.sonar.server.permission.PermissionTemplateService; -import org.sonar.server.permission.PermissionUpdater; -import org.sonar.server.permission.UserPermissionChanger; +import org.sonar.server.common.permission.DefaultTemplatesResolverImpl; +import org.sonar.server.common.permission.GroupPermissionChanger; +import org.sonar.server.common.permission.PermissionTemplateService; +import org.sonar.server.common.permission.PermissionUpdater; +import org.sonar.server.common.permission.UserPermissionChanger; import org.sonar.server.permission.index.PermissionIndexer; import org.sonar.server.permission.ws.PermissionsWsModule; import org.sonar.server.platform.ClusterVerification;