]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21819 Extract logic reusable between legacy and v2 endpoints.
authorWojtek Wajerowicz <115081248+wojciech-wajerowicz-sonarsource@users.noreply.github.com>
Tue, 19 Mar 2024 13:15:49 +0000 (14:15 +0100)
committersonartech <sonartech@sonarsource.com>
Thu, 28 Mar 2024 20:02:50 +0000 (20:02 +0000)
122 files changed:
server/sonar-webserver-common/build.gradle
server/sonar-webserver-common/src/it/java/org/sonar/server/common/component/ComponentUpdaterIT.java [new file with mode: 0644]
server/sonar-webserver-common/src/it/java/org/sonar/server/common/permission/DefaultTemplatesResolverImplIT.java [new file with mode: 0644]
server/sonar-webserver-common/src/it/java/org/sonar/server/common/permission/GroupPermissionChangerIT.java [new file with mode: 0644]
server/sonar-webserver-common/src/it/java/org/sonar/server/common/permission/PermissionTemplateServiceIT.java [new file with mode: 0644]
server/sonar-webserver-common/src/it/java/org/sonar/server/common/permission/UserPermissionChangerIT.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/almintegration/ProjectKeyGenerator.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/almintegration/package-info.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DelegatingDevOpsProjectCreatorFactory.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectCreator.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectCreatorFactory.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/DevOpsProjectDescriptor.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreationParameters.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreator.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorFactory.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/package-info.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreator.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorFactory.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/package-info.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/package-info.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/ComponentCreationParameters.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/ComponentUpdater.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/NewComponent.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/package-info.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/newcodeperiod/CaycUtils.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/newcodeperiod/NewCodeDefinitionResolver.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/newcodeperiod/package-info.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/DefaultTemplatesResolver.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/DefaultTemplatesResolverImpl.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/GranteeTypeSpecificPermissionUpdater.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/GroupPermissionChange.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/GroupPermissionChanger.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/PermissionChange.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/PermissionTemplateService.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/PermissionUpdater.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/UserPermissionChange.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/permission/UserPermissionChanger.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/ProjectCreator.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/project/package-info.java [new file with mode: 0644]
server/sonar-webserver-common/src/test/java/org/sonar/server/common/almintegration/ProjectKeyGeneratorTest.java [new file with mode: 0644]
server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/DelegatingDevOpsProjectCreatorFactoryTest.java [new file with mode: 0644]
server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorFactoryTest.java [new file with mode: 0644]
server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/github/GithubProjectCreatorTest.java [new file with mode: 0644]
server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorFactoryTest.java [new file with mode: 0644]
server/sonar-webserver-common/src/test/java/org/sonar/server/common/almsettings/gitlab/GitlabProjectCreatorTest.java [new file with mode: 0644]
server/sonar-webserver-common/src/test/java/org/sonar/server/common/newcodeperiod/CaycUtilsTest.java [new file with mode: 0644]
server/sonar-webserver-common/src/test/java/org/sonar/server/common/newcodeperiod/NewCodeDefinitionResolverTest.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/ce/queue/BranchReportSubmitterIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/ce/queue/ReportSubmitterIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ComponentUpdaterIT.java [deleted file]
server/sonar-webserver-webapi/src/it/java/org/sonar/server/newcodeperiod/CaycUtilsTest.java [deleted file]
server/sonar-webserver-webapi/src/it/java/org/sonar/server/newcodeperiod/NewCodeDefinitionResolverTest.java [deleted file]
server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/DefaultTemplatesResolverImplIT.java [deleted file]
server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/GroupPermissionChangerIT.java [deleted file]
server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/PermissionTemplateServiceIT.java [deleted file]
server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/UserPermissionChangerIT.java [deleted file]
server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/BasePermissionWsIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/ApplyTemplateActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/BulkApplyTemplateActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/DeleteTemplateActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/template/SearchTemplatesActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/project/ws/CreateActionIT.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/ProjectKeyGenerator.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DelegatingDevOpsProjectCreatorFactory.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectCreator.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectCreatorFactory.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectDescriptor.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreationParameters.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreator.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactory.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/gitlab/GitlabProjectCreator.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/gitlab/GitlabProjectCreatorFactory.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/gitlab/package-info.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/queue/ReportSubmitter.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentCreationParameters.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/NewComponent.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/CaycUtils.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/NewCodeDefinitionResolver.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/SetAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/UnsetAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/DefaultTemplatesResolver.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/DefaultTemplatesResolverImpl.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/GranteeTypeSpecificPermissionUpdater.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/GroupPermissionChange.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/GroupPermissionChanger.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/PermissionChange.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/PermissionTemplateService.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/PermissionUpdater.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/UserPermissionChange.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/UserPermissionChanger.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/AddGroupAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/AddUserAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/RemoveGroupAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/RemoveUserAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/ApplyTemplateAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/BulkApplyTemplateAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/DeleteTemplateAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/SearchTemplatesAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/SearchTemplatesData.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/CreateAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectCreator.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/ProjectKeyGeneratorTest.java [deleted file]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/DelegatingDevOpsProjectCreatorFactoryTest.java [deleted file]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactoryTest.java [deleted file]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorTest.java [deleted file]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/gitlab/GitlabProjectCreatorFactoryTest.java [deleted file]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/gitlab/GitlabProjectCreatorTest.java [deleted file]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/NewComponentTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/permission/ws/template/SearchTemplatesDataTest.java
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java

index d12d353e29bd59cbb932e9bc652214c25152187a..89ce28036572891118aa96169e1cad441df8ced3 100644 (file)
@@ -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 (file)
index 0000000..30a70a1
--- /dev/null
@@ -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<UserPermissionChange> 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<BranchDto> 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<BranchDto> 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<BranchDto> 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<BranchDto> 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<BranchDto> 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<String> 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 (file)
index 0000000..2a3716a
--- /dev/null
@@ -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 (file)
index 0000000..03e760b
--- /dev/null
@@ -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<String> 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 (file)
index 0000000..d7095c1
--- /dev/null
@@ -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<String> selectProjectPermissionsOfGroup(@Nullable GroupDto groupDto, String projectUuid) {
+    return dbTester.getDbClient().groupPermissionDao().selectEntityPermissionsOfGroup(session, groupDto != null ? groupDto.getUuid() : null, projectUuid);
+  }
+
+  private List<String> 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<PermissionTemplateDto> 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 (file)
index 0000000..479a28e
--- /dev/null
@@ -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<String> 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<String> 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 (file)
index 0000000..cf5a04a
--- /dev/null
@@ -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<String> 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 (file)
index 0000000..6cfcd9b
--- /dev/null
@@ -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 (file)
index 0000000..dc0660d
--- /dev/null
@@ -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<DevOpsProjectCreatorFactory> delegates;
+
+  public DelegatingDevOpsProjectCreatorFactory(Set<DevOpsProjectCreatorFactory> delegates) {
+    this.delegates = delegates;
+  }
+
+  @Override
+  public Optional<DevOpsProjectCreator> getDevOpsProjectCreator(DbSession dbSession, Map<String, String> characteristics) {
+    return delegates.stream()
+      .flatMap(delegate -> delegate.getDevOpsProjectCreator(dbSession, characteristics).stream())
+      .findFirst();
+  }
+
+  @Override
+  public Optional<DevOpsProjectCreator> 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 (file)
index 0000000..86225fa
--- /dev/null
@@ -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 (file)
index 0000000..55835f4
--- /dev/null
@@ -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<DevOpsProjectCreator> getDevOpsProjectCreator(DbSession dbSession, Map<String, String> characteristics);
+
+  Optional<DevOpsProjectCreator> 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 (file)
index 0000000..4459ccb
--- /dev/null
@@ -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 (file)
index 0000000..72b2ca8
--- /dev/null
@@ -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 (file)
index 0000000..47902dd
--- /dev/null
@@ -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<UserPermissionChange> 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<UserPermissionChange> 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<GithubPermissionsMappingDto> permissionsMappingDtos = dbClient.githubPermissionsMappingDao().findAll(dbClient.openSession(false));
+
+    boolean userHasDirectAccessToRepo = doesUserHaveScanPermission(organization, repository, permissionsMappingDtos);
+    if (userHasDirectAccessToRepo) {
+      return true;
+    }
+    return doesUserBelongToAGroupWithScanPermission(organization, repository, permissionsMappingDtos);
+  }
+
+  private boolean doesUserHaveScanPermission(String organization, String repository, Set<GithubPermissionsMappingDto> permissionsMappingDtos) {
+    Set<GsonRepositoryCollaborator> repositoryCollaborators = githubApplicationClient.getRepositoryCollaborators(devOpsProjectDescriptor.url(), 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<GithubPermissionsMappingDto> permissionsMappingDtos) {
+    Set<GsonRepositoryTeam> repositoryTeams = githubApplicationClient.getRepositoryTeams(devOpsProjectDescriptor.url(), authAppInstallationToken, organization, repository);
+
+    Set<String> groupsOfUser = findUserMembershipOnSonarQube(organization);
+    return repositoryTeams.stream()
+      .filter(team -> hasScanPermission(permissionsMappingDtos, team.permission(), team.permissions()))
+      .map(GsonRepositoryTeam::name)
+      .anyMatch(groupsOfUser::contains);
+  }
+
+  private Set<String> findUserMembershipOnSonarQube(String organization) {
+    return userSession.getGroups().stream()
+      .map(GroupDto::getName)
+      .filter(groupName -> groupName.contains("/"))
+      .map(name -> name.replaceFirst(organization + "/", ""))
+      .collect(toSet());
+  }
+
+  private boolean hasScanPermission(Set<GithubPermissionsMappingDto> permissionsMappingDtos, String role, GsonRepositoryPermissions permissions) {
+    Set<String> sonarqubePermissions = githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(permissionsMappingDtos,
+      role, permissions);
+    return sonarqubePermissions.contains(UserRole.SCAN);
+  }
+
+  @Override
+  public ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, CreationMethod creationMethod, 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 (file)
index 0000000..2a1821c
--- /dev/null
@@ -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<UserPermissionChange> 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<UserPermissionChange> 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<DevOpsProjectCreator> getDevOpsProjectCreator(DbSession dbSession, Map<String, String> characteristics) {
+    String githubApiUrl = characteristics.get(DEVOPS_PLATFORM_URL);
+    String githubRepository = characteristics.get(DEVOPS_PLATFORM_PROJECT_IDENTIFIER);
+    if (githubApiUrl == null || githubRepository == null) {
+      return Optional.empty();
+    }
+    DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, githubApiUrl, githubRepository);
+
+    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<DevOpsProjectCreator> 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<AppInstallationToken> 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<DevOpsProjectCreator> 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<AppInstallationToken> 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<AppInstallationToken> 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<Long> findInstallationIdToAccessRepo(GithubAppConfiguration githubAppConfiguration, String repositoryKey) {
+    return githubApplicationClient.getInstallationId(githubAppConfiguration, repositoryKey);
+  }
+
+  private AppInstallationToken generateAppInstallationToken(GithubAppConfiguration githubAppConfiguration, long installationId) {
+    return githubApplicationClient.createAppInstallationToken(githubAppConfiguration, installationId)
+      .orElseThrow(() -> new IllegalStateException(format("Error while generating token for GitHub Api Url %s (installation id: %s)",
+        githubAppConfiguration.getApiEndpoint(), installationId)));
+  }
+
+}
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 (file)
index 0000000..651c671
--- /dev/null
@@ -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 (file)
index 0000000..2c530a0
--- /dev/null
@@ -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<String> 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> 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<String> getDefaultBranchOnGitlab(String gitlabUrl, String pat, long gitlabProjectId) {
+    Optional<GitLabBranch> 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 (file)
index 0000000..19176e8
--- /dev/null
@@ -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<DevOpsProjectCreator> getDevOpsProjectCreator(DbSession dbSession, Map<String, String> characteristics) {
+    return Optional.empty();
+  }
+
+  @Override
+  public Optional<DevOpsProjectCreator> 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 (file)
index 0000000..f5cc5a1
--- /dev/null
@@ -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 (file)
index 0000000..2883960
--- /dev/null
@@ -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 (file)
index 0000000..4ba5d2c
--- /dev/null
@@ -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 (file)
index 0000000..e42e77a
--- /dev/null
@@ -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<String> 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<UserPermissionChange> userPermissionUpdater;
+  private final PermissionService permissionService;
+
+  public ComponentUpdater(DbClient dbClient, I18n i18n, System2 system2,
+    PermissionTemplateService permissionTemplateService, FavoriteUpdater favoriteUpdater,
+    Indexers indexers, UuidFactory uuidFactory, DefaultBranchNameResolver defaultBranchNameResolver, PermissionUpdater<UserPermissionChange> 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<ComponentDto> 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 (file)
index 0000000..8167bda
--- /dev/null
@@ -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 (file)
index 0000000..51d90d8
--- /dev/null
@@ -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 (file)
index 0000000..b206539
--- /dev/null
@@ -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 (file)
index 0000000..8956831
--- /dev/null
@@ -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 = "<ul>";
+
+  private static final String END_LIST = "</ul>";
+  private static final String BEGIN_ITEM_LIST = "<li>";
+  private static final String END_ITEM_LIST = "</li>";
+
+  public static final String NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION = "Project New Code Definition Type<br/>" +
+    "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<br/>" +
+    "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<NewCodePeriodType> 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<String> 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 (file)
index 0000000..47a06a5
--- /dev/null
@@ -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 (file)
index 0000000..471d497
--- /dev/null
@@ -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}.
+   * <ul>
+   *   <li>{@link ResolvedDefaultTemplates#project} is always the same as {@link DefaultTemplates#projectUuid}</li>
+   *   <li>when Governance is not installed, {@link ResolvedDefaultTemplates#application} is always {@code null}</li>
+   *   <li>when Governance is installed, {@link ResolvedDefaultTemplates#application} is  {@link DefaultTemplates#applicationsUuid}
+   *       when it is non {@code null}, otherwise it is {@link DefaultTemplates#projectUuid}</li>
+   *   <li>when Governance is installed, {@link ResolvedDefaultTemplates#portfolio} is  {@link DefaultTemplates#portfoliosUuid}
+   *       when it is non {@code null}, otherwise it is {@link DefaultTemplates#projectUuid}</li>
+   * </ul>
+   */
+  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<String> getApplication() {
+      return ofNullable(application);
+    }
+
+    public Optional<String> 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 (file)
index 0000000..ec32b6d
--- /dev/null
@@ -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 (file)
index 0000000..76a7602
--- /dev/null
@@ -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<T extends PermissionChange> {
+  Class<T> getHandledClass();
+
+  Set<String> loadExistingEntityPermissions(DbSession dbSession, String uuidOfGrantee, @Nullable String entityUuid);
+
+  boolean apply(DbSession dbSession, Set<String> 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 (file)
index 0000000..cb65536
--- /dev/null
@@ -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<String> 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 (file)
index 0000000..23c893e
--- /dev/null
@@ -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<GroupPermissionChange> {
+
+  private final DbClient dbClient;
+  private final UuidFactory uuidFactory;
+
+  public GroupPermissionChanger(DbClient dbClient, UuidFactory uuidFactory) {
+    this.dbClient = dbClient;
+    this.uuidFactory = uuidFactory;
+  }
+
+  @Override
+  public Class<GroupPermissionChange> getHandledClass() {
+    return GroupPermissionChange.class;
+  }
+
+  @Override
+  public Set<String> 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<String> 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 (file)
index 0000000..30173c3
--- /dev/null
@@ -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 (file)
index 0000000..3857d0c
--- /dev/null
@@ -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<String> 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<EntityDto> 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<PermissionTemplateUserDto> usersPermissions = dbClient.permissionTemplateDao().selectUserPermissionsByTemplateId(dbSession, template.getUuid());
+    Set<String> permissionTemplateUserUuids = usersPermissions.stream().map(PermissionTemplateUserDto::getUserUuid).collect(Collectors.toSet());
+    Map<String, UserId> 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<PermissionTemplateGroupDto> 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<PermissionTemplateCharacteristicDto> characteristics = dbClient.permissionTemplateCharacteristicDao().selectByTemplateUuids(dbSession, singletonList(template.getUuid()));
+    if (projectCreatorUserUuid != null) {
+      Set<String> 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<PermissionTemplateDto> allPermissionTemplates = dbClient.permissionTemplateDao().selectAll(dbSession, null);
+    List<PermissionTemplateDto> 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<PermissionTemplateDto> matchingTemplates) {
+    if (matchingTemplates.size() > 1) {
+      StringBuilder templatesNames = new StringBuilder();
+      for (Iterator<PermissionTemplateDto> 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 (file)
index 0000000..e2beb97
--- /dev/null
@@ -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<T extends PermissionChange> {
+
+  private final Indexers indexers;
+
+  private final Map<Class<?>, GranteeTypeSpecificPermissionUpdater<T>> specificPermissionClassToHandler;
+
+  public PermissionUpdater(Indexers indexers, Set<GranteeTypeSpecificPermissionUpdater<T>> permissionChangers) {
+    this.indexers = indexers;
+    specificPermissionClassToHandler = permissionChangers.stream()
+      .collect(toMap(GranteeTypeSpecificPermissionUpdater::getHandledClass, Function.identity()));
+  }
+
+  public void apply(DbSession dbSession, Collection<T> changes) {
+    checkState(changes.stream().map(PermissionChange::getProjectUuid).distinct().count() <= 1,
+      "Only one project per changes is supported");
+
+    List<String> projectOrViewUuids = new ArrayList<>();
+    Map<Optional<String>, List<T>> 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<String> projectOrViewUuids, List<T> permissionChanges) {
+    T anyPermissionChange = permissionChanges.iterator().next();
+    EntityDto entity = anyPermissionChange.getEntity();
+    String entityUuid = Optional.ofNullable(entity).map(EntityDto::getUuid).orElse(null);
+    GranteeTypeSpecificPermissionUpdater<T> granteeTypeSpecificPermissionUpdater = getSpecificProjectUpdater(anyPermissionChange);
+    Set<String> 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<T> 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 (file)
index 0000000..acb5c26
--- /dev/null
@@ -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 (file)
index 0000000..3874f7b
--- /dev/null
@@ -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<UserPermissionChange> {
+
+  private final DbClient dbClient;
+  private final UuidFactory uuidFactory;
+
+  public UserPermissionChanger(DbClient dbClient, UuidFactory uuidFactory) {
+    this.dbClient = dbClient;
+    this.uuidFactory = uuidFactory;
+  }
+
+  @Override
+  public Class<UserPermissionChange> getHandledClass() {
+    return UserPermissionChange.class;
+  }
+
+  @Override
+  public Set<String> 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<String> 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<String> 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<String> 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 (file)
index 0000000..22cf072
--- /dev/null
@@ -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 (file)
index 0000000..174ee7f
--- /dev/null
@@ -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 (file)
index 0000000..3751d75
--- /dev/null
@@ -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 (file)
index 0000000..b881560
--- /dev/null
@@ -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<String, String> CHARACTERISTICS = Map.of("toto", "tata");
+
+  @Test
+  public void getDevOpsProjectDescriptor_whenNoDelegates_shouldReturnEmptyOptional() {
+    DelegatingDevOpsProjectCreatorFactory noDelegates = new DelegatingDevOpsProjectCreatorFactory(emptySet());
+    Optional<DevOpsProjectCreator> devOpsProjectCreator = noDelegates.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS);
+    assertThat(devOpsProjectCreator).isEmpty();
+  }
+
+  @Test
+  public void getDevOpsProjectDescriptor_whenNoDelegatesReturningACreator_shouldReturnEmptyOptional() {
+    DelegatingDevOpsProjectCreatorFactory delegates = new DelegatingDevOpsProjectCreatorFactory(Set.of(mock(), mock()));
+    Optional<DevOpsProjectCreator> devOpsProjectCreator = delegates.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS);
+
+    assertThat(devOpsProjectCreator).isEmpty();
+  }
+
+  @Test
+  public void getDevOpsProjectDescriptor_whenOneDelegatesReturningACreator_shouldDelegate() {
+    DevOpsProjectCreatorFactory successfulDelegate = mock();
+    DevOpsProjectCreator devOpsProjectCreator = mock();
+    when(successfulDelegate.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS)).thenReturn(Optional.of(devOpsProjectCreator));
+    DelegatingDevOpsProjectCreatorFactory delegates = new DelegatingDevOpsProjectCreatorFactory(Set.of(mock(), successfulDelegate));
+
+    assertThat(delegates.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS)).contains(devOpsProjectCreator);
+  }
+
+}
diff --git a/server/sonar-webserver-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 (file)
index 0000000..ac805ea
--- /dev/null
@@ -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<String, String> VALID_GITHUB_PROJECT_COORDINATES = Map.of(
+    DEVOPS_PLATFORM_URL, GITHUB_PROJECT_DESCRIPTOR.url(),
+    DEVOPS_PLATFORM_PROJECT_IDENTIFIER, GITHUB_PROJECT_DESCRIPTOR.projectIdentifier());
+  private static final long APP_INSTALLATION_ID = 534534534543L;
+  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<UserPermissionChange> permissionUpdater;
+  @Mock
+  private ManagedProjectService managedProjectService;
+  @Mock
+  private ProjectCreator projectCreator;
+
+  @InjectMocks
+  private GithubProjectCreatorFactory githubProjectCreatorFactory;
+
+  @Test
+  public void getDevOpsProjectCreator_whenNoCharacteristics_shouldReturnEmpty() {
+    Optional<DevOpsProjectCreator> devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, Map.of());
+
+    assertThat(devOpsProjectCreator).isEmpty();
+  }
+
+  @Test
+  public void getDevOpsProjectCreator_whenValidCharacteristicsButNoAlmSettingDao_shouldReturnEmpty() {
+    Optional<DevOpsProjectCreator> 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> 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 (file)
index 0000000..dcff559
--- /dev/null
@@ -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<UserPermissionChange> permissionUpdater;
+  @Mock
+  private ManagedProjectService managedProjectService;
+  @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+  private ProjectDefaultVisibility projectDefaultVisibility;
+  private final GitHubSettings gitHubSettings = mock();
+
+  private GithubProjectCreator githubProjectCreator;
+
+  @Captor
+  ArgumentCaptor<ComponentCreationParameters> componentCreationParametersCaptor;
+  @Captor
+  ArgumentCaptor<ProjectAlmSettingDto> 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<GsonRepositoryCollaborator> 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<GithubPermissionsMappingDto> 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<GithubPermissionsMappingDto> githubPermissionsMappingDtos = mockPermissionsMappingsDtos();
+    lenient().when(githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(githubPermissionsMappingDtos, team.permission(), team.permissions()))
+      .thenReturn(Arrays.stream(sqPermissions).collect(toSet()));
+  }
+
+  private Set<GithubPermissionsMappingDto> mockPermissionsMappingsDtos() {
+    Set<GithubPermissionsMappingDto> githubPermissionsMappingDtos = Set.of(mock(GithubPermissionsMappingDto.class));
+    when(dbClient.githubPermissionsMappingDao().findAll(any())).thenReturn(githubPermissionsMappingDtos);
+    return githubPermissionsMappingDtos;
+  }
+
+  private void bindGroupsToUser(String... groupNames) {
+    Set<GroupDto> groupDtos = Arrays.stream(groupNames)
+      .map(groupName -> new GroupDto().setName(ORGANIZATION_NAME + "/" + groupName).setUuid("uuid_" + groupName))
+      .collect(toSet());
+    when(userSession.getGroups()).thenReturn(groupDtos);
+  }
+
+  @Test
+  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<Collection<UserPermissionChange>> 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 (file)
index 0000000..2abdd27
--- /dev/null
@@ -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 (file)
index 0000000..76e2d2d
--- /dev/null
@@ -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<ProjectAlmSettingDto> 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<ProjectAlmSettingDto> 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 (file)
index 0000000..0afe761
--- /dev/null
@@ -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 (file)
index 0000000..6bba333
--- /dev/null
@@ -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> 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> 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> 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"));
+  }
+
+}
index 40455a782088c81f01438df0fea7c717f011638f..1ffde3bd74edfc6a20462da40160a3e472f2086a 100644 (file)
@@ -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;
index 1405595b259ff1977f108dd0a6753e0c67b29334..8dab56edddb7e83de4f624019ff6fb40ab1f04b4 100644 (file)
@@ -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;
index 1ddecc7a5248eb5fcc3880132721a6dbefcd4b6d..55cb770a561d2d7aedd209ff0129de67f3a0f85b 100644 (file)
@@ -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;
index fed6d8d4f8072ff84a104646142eef3c000566a1..0bd1deaa6a1debda83fb6289d7d7b33a53c8497a 100644 (file)
@@ -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;
index 9d0b82db32542a38d8cf692295e48562bde637c3..a9914d3c810c5b3cf93a71c98de89f635a6360fa 100644 (file)
@@ -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;
index 623c04f0f8134c302bde965459a45c4ee1aeef77..a71aa16951f4600ddd1c8bc5ae6fdc7fd7d3debe 100644 (file)
@@ -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;
index 2e8b1e1e13488a32f3079a619f7215588fbc651d..ef5e940f5676235fb7d374d6507d5491b192fd67 100644 (file)
@@ -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 (file)
index 11077ed..0000000
+++ /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<UserPermissionChange> 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<BranchDto> 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<BranchDto> 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<BranchDto> 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<BranchDto> 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<BranchDto> 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<String> 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 (file)
index c69f1db..0000000
+++ /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 (file)
index 332918f..0000000
+++ /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> 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> 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> 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 (file)
index 337e363..0000000
+++ /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 (file)
index 10c8f1a..0000000
+++ /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<String> 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 (file)
index e0b8344..0000000
+++ /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<String> selectProjectPermissionsOfGroup(@Nullable GroupDto groupDto, String projectUuid) {
-    return dbTester.getDbClient().groupPermissionDao().selectEntityPermissionsOfGroup(session, groupDto != null ? groupDto.getUuid() : null, projectUuid);
-  }
-
-  private List<String> 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<PermissionTemplateDto> 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 (file)
index f98ff2a..0000000
+++ /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<String> 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<String> existingPermissions) {
-    underTest.apply(db.getSession(), existingPermissions, change);
-    db.commit();
-  }
-}
index f40a9adfc2de62017d72656d9c34355ad0cc25bb..70687fc68b25e7b3ef57f99ba82d6a0985ee4e06 100644 (file)
@@ -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;
index 80482347f5fa8163848e883b1f2af2aa5b48b298..47f1ca2f1ca9ae94cabd7f713efef394bc1bb405 100644 (file)
@@ -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;
 
index 52f93e53f5cc9190a1ea5689dce20a9d4965d38e..f66366b10f4eba99fdcf6b1e4f2cce605925c35c 100644 (file)
@@ -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;
index 1bb8621fbc81d00217a85c22a3b7f0fc036fc22a..3b52322edecd7da720517e5576d348ac64facd86 100644 (file)
@@ -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;
index ff91930e3be891014e90a5b34d458b9e87440264..6322ebe7a84f70aa02442c9be0019c7aaa251ee8 100644 (file)
@@ -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;
index 42863cddfc0575d57687dfc7217b8a4addfeb263..305dd991f9791d3650215647938204bfba8ef730 100644 (file)
@@ -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 (file)
index 4a84afd..0000000
+++ /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<String> 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;
-  }
-
-}
index 88bfc22573c6afdafa9e224669d11ba4602ed83e..93d5001ed57148a39827c4933d885a51188158db 100644 (file)
@@ -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;
index 3834697bd0b74dcf4d6460982b1bb8f271666b8b..0f77f2fe2b61f315de0048eaef767f7e05e6279e 100644 (file)
@@ -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;
index 9d7e589022ecca993ff826bbbb49ce1129b645d9..6fc2d1bcb65ce5d965eb001c338ddaa161b79456 100644 (file)
@@ -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;
index 73632870db6b35f0a83bcfdcd955ebee4388cbaf..130078e23287face93558e5eaf322985b340a9af 100644 (file)
@@ -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;
index 264596f06f2b6ac7ebde1144e7e0f47f719180da..dc5e0ca7e0ace3c25a2b44b15b7b1bda434a87e9 100644 (file)
@@ -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 (file)
index 1adf4d6..0000000
+++ /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<DevOpsProjectCreatorFactory> delegates;
-
-  public DelegatingDevOpsProjectCreatorFactory(Set<DevOpsProjectCreatorFactory> delegates) {
-    this.delegates = delegates;
-  }
-
-  @Override
-  public Optional<DevOpsProjectCreator> getDevOpsProjectCreator(DbSession dbSession, Map<String, String> characteristics) {
-    return delegates.stream()
-      .flatMap(delegate -> delegate.getDevOpsProjectCreator(dbSession, characteristics).stream())
-      .findFirst();
-  }
-
-  @Override
-  public Optional<DevOpsProjectCreator> 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 (file)
index 3c18b3a..0000000
+++ /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 (file)
index f409d2d..0000000
+++ /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<DevOpsProjectCreator> getDevOpsProjectCreator(DbSession dbSession, Map<String, String> characteristics);
-
-  Optional<DevOpsProjectCreator> 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 (file)
index e7df424..0000000
+++ /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 (file)
index dd4d191..0000000
+++ /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 (file)
index 8b1ca0d..0000000
+++ /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<UserPermissionChange> 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<UserPermissionChange> 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<GithubPermissionsMappingDto> permissionsMappingDtos = dbClient.githubPermissionsMappingDao().findAll(dbClient.openSession(false));
-
-    boolean userHasDirectAccessToRepo = doesUserHaveScanPermission(organization, repository, permissionsMappingDtos);
-    if (userHasDirectAccessToRepo) {
-      return true;
-    }
-    return doesUserBelongToAGroupWithScanPermission(organization, repository, permissionsMappingDtos);
-  }
-
-  private boolean doesUserHaveScanPermission(String organization, String repository, Set<GithubPermissionsMappingDto> permissionsMappingDtos) {
-    Set<GsonRepositoryCollaborator> repositoryCollaborators = githubApplicationClient.getRepositoryCollaborators(devOpsProjectDescriptor.url(), 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<GithubPermissionsMappingDto> permissionsMappingDtos) {
-    Set<GsonRepositoryTeam> repositoryTeams = githubApplicationClient.getRepositoryTeams(devOpsProjectDescriptor.url(), authAppInstallationToken, organization, repository);
-
-    Set<String> groupsOfUser = findUserMembershipOnSonarQube(organization);
-    return repositoryTeams.stream()
-      .filter(team -> hasScanPermission(permissionsMappingDtos, team.permission(), team.permissions()))
-      .map(GsonRepositoryTeam::name)
-      .anyMatch(groupsOfUser::contains);
-  }
-
-  private Set<String> findUserMembershipOnSonarQube(String organization) {
-    return userSession.getGroups().stream()
-      .map(GroupDto::getName)
-      .filter(groupName -> groupName.contains("/"))
-      .map(name -> name.replaceFirst(organization + "/", ""))
-      .collect(toSet());
-  }
-
-  private boolean hasScanPermission(Set<GithubPermissionsMappingDto> permissionsMappingDtos, String role, GsonRepositoryPermissions permissions) {
-    Set<String> sonarqubePermissions = githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(permissionsMappingDtos,
-      role, permissions);
-    return sonarqubePermissions.contains(UserRole.SCAN);
-  }
-
-  @Override
-  public ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, CreationMethod creationMethod, 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 (file)
index 935a8b1..0000000
+++ /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<UserPermissionChange> 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<UserPermissionChange> 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<DevOpsProjectCreator> getDevOpsProjectCreator(DbSession dbSession, Map<String, String> characteristics) {
-    String githubApiUrl = characteristics.get(DEVOPS_PLATFORM_URL);
-    String githubRepository = characteristics.get(DEVOPS_PLATFORM_PROJECT_IDENTIFIER);
-    if (githubApiUrl == null || githubRepository == null) {
-      return Optional.empty();
-    }
-    DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, githubApiUrl, githubRepository);
-
-    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<DevOpsProjectCreator> 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<AppInstallationToken> 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<DevOpsProjectCreator> 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<AppInstallationToken> 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<AppInstallationToken> 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<Long> findInstallationIdToAccessRepo(GithubAppConfiguration githubAppConfiguration, String repositoryKey) {
-    return githubApplicationClient.getInstallationId(githubAppConfiguration, repositoryKey);
-  }
-
-  private AppInstallationToken generateAppInstallationToken(GithubAppConfiguration githubAppConfiguration, long installationId) {
-    return githubApplicationClient.createAppInstallationToken(githubAppConfiguration, installationId)
-      .orElseThrow(() -> new IllegalStateException(format("Error while generating token for GitHub Api Url %s (installation id: %s)",
-        githubAppConfiguration.getApiEndpoint(), installationId)));
-  }
-
-}
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 (file)
index ccf025a..0000000
+++ /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<String> 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> 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<String> getDefaultBranchOnGitlab(String gitlabUrl, String pat, long gitlabProjectId) {
-    Optional<GitLabBranch> 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 (file)
index 1e32d82..0000000
+++ /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<DevOpsProjectCreator> getDevOpsProjectCreator(DbSession dbSession, Map<String, String> characteristics) {
-    return Optional.empty();
-  }
-
-  @Override
-  public Optional<DevOpsProjectCreator> 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 (file)
index 86fc9b5..0000000
+++ /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;
index 84de5ba534d22851eeec5e7856319e1b5617a7ae..7a86d12ee747bd2d8cd123f029aecf5880d30f27 100644 (file)
@@ -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 (file)
index 84879ca..0000000
+++ /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 (file)
index 18ede19..0000000
+++ /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<String> 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<UserPermissionChange> userPermissionUpdater;
-  private final PermissionService permissionService;
-
-  public ComponentUpdater(DbClient dbClient, I18n i18n, System2 system2,
-    PermissionTemplateService permissionTemplateService, FavoriteUpdater favoriteUpdater,
-    Indexers indexers, UuidFactory uuidFactory, DefaultBranchNameResolver defaultBranchNameResolver, PermissionUpdater<UserPermissionChange> 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<ComponentDto> 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 (file)
index 3429bb5..0000000
+++ /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 (file)
index f2ef6a3..0000000
+++ /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 (file)
index 12491a1..0000000
+++ /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 = "<ul>";
-
-  private static final String END_LIST = "</ul>";
-  private static final String BEGIN_ITEM_LIST = "<li>";
-  private static final String END_ITEM_LIST = "</li>";
-
-  public static final String NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION = "Project New Code Definition Type<br/>" +
-    "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<br/>" +
-    "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<NewCodePeriodType> 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<String> 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);
-  }
-
-}
index 60c581f6accf9e04aa92cd0d37f7d5560e91bacf..dc5a067e5baa3860d3e779814b2ad022de634e8d 100644 (file)
@@ -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;
index 92609739a83c930709d6e2c510af14b8b75798d6..b656e4c2e23af01956ed431a5be0a674bd52c850 100644 (file)
@@ -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 (file)
index 0161347..0000000
+++ /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}.
-   * <ul>
-   *   <li>{@link ResolvedDefaultTemplates#project} is always the same as {@link DefaultTemplates#projectUuid}</li>
-   *   <li>when Governance is not installed, {@link ResolvedDefaultTemplates#application} is always {@code null}</li>
-   *   <li>when Governance is installed, {@link ResolvedDefaultTemplates#application} is  {@link DefaultTemplates#applicationsUuid}
-   *       when it is non {@code null}, otherwise it is {@link DefaultTemplates#projectUuid}</li>
-   *   <li>when Governance is installed, {@link ResolvedDefaultTemplates#portfolio} is  {@link DefaultTemplates#portfoliosUuid}
-   *       when it is non {@code null}, otherwise it is {@link DefaultTemplates#projectUuid}</li>
-   * </ul>
-   */
-  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<String> getApplication() {
-      return ofNullable(application);
-    }
-
-    public Optional<String> 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 (file)
index d1c62f9..0000000
+++ /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 (file)
index 7965a5a..0000000
+++ /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<T extends PermissionChange> {
-  Class<T> getHandledClass();
-
-  Set<String> loadExistingEntityPermissions(DbSession dbSession, String uuidOfGrantee, @Nullable String entityUuid);
-
-  boolean apply(DbSession dbSession, Set<String> 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 (file)
index 18b9211..0000000
+++ /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<String> 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 (file)
index 004b6c0..0000000
+++ /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<GroupPermissionChange> {
-
-  private final DbClient dbClient;
-  private final UuidFactory uuidFactory;
-
-  public GroupPermissionChanger(DbClient dbClient, UuidFactory uuidFactory) {
-    this.dbClient = dbClient;
-    this.uuidFactory = uuidFactory;
-  }
-
-  @Override
-  public Class<GroupPermissionChange> getHandledClass() {
-    return GroupPermissionChange.class;
-  }
-
-  @Override
-  public Set<String> 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<String> 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 (file)
index 1e96e72..0000000
+++ /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 (file)
index 2b2bd60..0000000
+++ /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<String> 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<EntityDto> 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<PermissionTemplateUserDto> usersPermissions = dbClient.permissionTemplateDao().selectUserPermissionsByTemplateId(dbSession, template.getUuid());
-    Set<String> permissionTemplateUserUuids = usersPermissions.stream().map(PermissionTemplateUserDto::getUserUuid).collect(Collectors.toSet());
-    Map<String, UserId> 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<PermissionTemplateGroupDto> 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<PermissionTemplateCharacteristicDto> characteristics = dbClient.permissionTemplateCharacteristicDao().selectByTemplateUuids(dbSession, singletonList(template.getUuid()));
-    if (projectCreatorUserUuid != null) {
-      Set<String> 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<PermissionTemplateDto> allPermissionTemplates = dbClient.permissionTemplateDao().selectAll(dbSession, null);
-    List<PermissionTemplateDto> 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<PermissionTemplateDto> matchingTemplates) {
-    if (matchingTemplates.size() > 1) {
-      StringBuilder templatesNames = new StringBuilder();
-      for (Iterator<PermissionTemplateDto> 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 (file)
index db9e77e..0000000
+++ /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<T extends PermissionChange> {
-
-  private final Indexers indexers;
-
-  private final Map<Class<?>, GranteeTypeSpecificPermissionUpdater<T>> specificPermissionClassToHandler;
-
-  public PermissionUpdater(Indexers indexers, Set<GranteeTypeSpecificPermissionUpdater<T>> permissionChangers) {
-    this.indexers = indexers;
-    specificPermissionClassToHandler = permissionChangers.stream()
-      .collect(toMap(GranteeTypeSpecificPermissionUpdater::getHandledClass, Function.identity()));
-  }
-
-  public void apply(DbSession dbSession, Collection<T> changes) {
-    checkState(changes.stream().map(PermissionChange::getProjectUuid).distinct().count() <= 1,
-      "Only one project per changes is supported");
-
-    List<String> projectOrViewUuids = new ArrayList<>();
-    Map<Optional<String>, List<T>> 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<String> projectOrViewUuids, List<T> permissionChanges) {
-    T anyPermissionChange = permissionChanges.iterator().next();
-    EntityDto entity = anyPermissionChange.getEntity();
-    String entityUuid = Optional.ofNullable(entity).map(EntityDto::getUuid).orElse(null);
-    GranteeTypeSpecificPermissionUpdater<T> granteeTypeSpecificPermissionUpdater = getSpecificProjectUpdater(anyPermissionChange);
-    Set<String> 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<T> 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 (file)
index 013edc6..0000000
+++ /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 (file)
index 0df91b7..0000000
+++ /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<UserPermissionChange> {
-
-  private final DbClient dbClient;
-  private final UuidFactory uuidFactory;
-
-  public UserPermissionChanger(DbClient dbClient, UuidFactory uuidFactory) {
-    this.dbClient = dbClient;
-    this.uuidFactory = uuidFactory;
-  }
-
-  @Override
-  public Class<UserPermissionChange> getHandledClass() {
-    return UserPermissionChange.class;
-  }
-
-  @Override
-  public Set<String> 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<String> 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<String> 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<String> 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());
-    }
-  }
-
-}
index aef48dd5404231028624034ba4a73ba95369e436..43d07ffe23a7c01326a5903c0356c590b8cff700 100644 (file)
@@ -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;
index ea06411099deea035974a2f59c6aafcbe643507e..d2d8203a986407ca5150b1bb82233a2b554cdabc 100644 (file)
@@ -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;
index 1cf108198bc76e155f13fa59d255cd607d220d99..80933e06d99ebcc61c8fa9cf6e9db859b3f9099b 100644 (file)
@@ -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;
index b902a82d6f2686accf7f2e2848ddc9ad38595255..6ec116a2d3dc8902511b333d19fb9ea24a9b8b7a 100644 (file)
@@ -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;
index 58503cd8ee88054d86df914449b5e585f70f4975..dcd4325dc7098ae3688a53ed0811d55a6ff48ac4 100644 (file)
@@ -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;
index c5757b0699500af617160190d44714ee5d75e1ee..e5270df26aae050f94fc4869424903125de2e8cf 100644 (file)
@@ -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;
index 270aeb6268b317cd65add1aceb8d705226f0dad5..d60696baa9c052681404d66aea983e351d028856 100644 (file)
@@ -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;
index 4387439169ba0edded6d94542fb4580b4d779835..e71c075ccfcae9337bb5e1dab8477215affb7c6f 100644 (file)
@@ -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;
index d5f52297710087127c55a361ec4c15f84d6c9ac3..733e859ab7793fda5071ef524bb0c349d11fd599 100644 (file)
@@ -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;
index 9dde9e80e4c88409ed51bccceb0419475268775f..d76aff13413a0b911faaffc61a88cc32219b8e09 100644 (file)
@@ -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 (file)
index 219ed69..0000000
+++ /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);
-  }
-}
index 358b9317b5b7784bf5b05fd3b3f14a8e29a084be..70ac999aee8e85dc940841afb2be9d8b9793c1ed 100644 (file)
@@ -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 (file)
index 576c925..0000000
+++ /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 (file)
index 8aa9ab2..0000000
+++ /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<String, String> CHARACTERISTICS = Map.of("toto", "tata");
-
-  @Test
-  public void getDevOpsProjectDescriptor_whenNoDelegates_shouldReturnEmptyOptional() {
-    DelegatingDevOpsProjectCreatorFactory noDelegates = new DelegatingDevOpsProjectCreatorFactory(emptySet());
-    Optional<DevOpsProjectCreator> devOpsProjectCreator = noDelegates.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS);
-    assertThat(devOpsProjectCreator).isEmpty();
-  }
-
-  @Test
-  public void getDevOpsProjectDescriptor_whenNoDelegatesReturningACreator_shouldReturnEmptyOptional() {
-    DelegatingDevOpsProjectCreatorFactory delegates = new DelegatingDevOpsProjectCreatorFactory(Set.of(mock(), mock()));
-    Optional<DevOpsProjectCreator> devOpsProjectCreator = delegates.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS);
-
-    assertThat(devOpsProjectCreator).isEmpty();
-  }
-
-  @Test
-  public void getDevOpsProjectDescriptor_whenOneDelegatesReturningACreator_shouldDelegate() {
-    DevOpsProjectCreatorFactory successfulDelegate = mock();
-    DevOpsProjectCreator devOpsProjectCreator = mock();
-    when(successfulDelegate.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS)).thenReturn(Optional.of(devOpsProjectCreator));
-    DelegatingDevOpsProjectCreatorFactory delegates = new DelegatingDevOpsProjectCreatorFactory(Set.of(mock(), successfulDelegate));
-
-    assertThat(delegates.getDevOpsProjectCreator(DB_SESSION, CHARACTERISTICS)).contains(devOpsProjectCreator);
-  }
-
-}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactoryTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactoryTest.java
deleted file mode 100644 (file)
index f1d153a..0000000
+++ /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<String, String> VALID_GITHUB_PROJECT_COORDINATES = Map.of(
-    DEVOPS_PLATFORM_URL, GITHUB_PROJECT_DESCRIPTOR.url(),
-    DEVOPS_PLATFORM_PROJECT_IDENTIFIER, GITHUB_PROJECT_DESCRIPTOR.projectIdentifier());
-  private static final long APP_INSTALLATION_ID = 534534534543L;
-  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<UserPermissionChange> permissionUpdater;
-  @Mock
-  private ManagedProjectService managedProjectService;
-  @Mock
-  private ProjectCreator projectCreator;
-
-  @InjectMocks
-  private GithubProjectCreatorFactory githubProjectCreatorFactory;
-
-  @Test
-  public void getDevOpsProjectCreator_whenNoCharacteristics_shouldReturnEmpty() {
-    Optional<DevOpsProjectCreator> devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, Map.of());
-
-    assertThat(devOpsProjectCreator).isEmpty();
-  }
-
-  @Test
-  public void getDevOpsProjectCreator_whenValidCharacteristicsButNoAlmSettingDao_shouldReturnEmpty() {
-    Optional<DevOpsProjectCreator> 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> 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 (file)
index 6198464..0000000
+++ /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<UserPermissionChange> permissionUpdater;
-  @Mock
-  private ManagedProjectService managedProjectService;
-  @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-  private ProjectDefaultVisibility projectDefaultVisibility;
-  private final GitHubSettings gitHubSettings = mock();
-
-  private GithubProjectCreator githubProjectCreator;
-
-  @Captor
-  ArgumentCaptor<ComponentCreationParameters> componentCreationParametersCaptor;
-  @Captor
-  ArgumentCaptor<ProjectAlmSettingDto> 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<GsonRepositoryCollaborator> 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<GithubPermissionsMappingDto> 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<GithubPermissionsMappingDto> githubPermissionsMappingDtos = mockPermissionsMappingsDtos();
-    lenient().when(githubPermissionConverter.toSonarqubeRolesWithFallbackOnRepositoryPermissions(githubPermissionsMappingDtos, team.permission(), team.permissions()))
-      .thenReturn(Arrays.stream(sqPermissions).collect(toSet()));
-  }
-
-  private Set<GithubPermissionsMappingDto> mockPermissionsMappingsDtos() {
-    Set<GithubPermissionsMappingDto> githubPermissionsMappingDtos = Set.of(mock(GithubPermissionsMappingDto.class));
-    when(dbClient.githubPermissionsMappingDao().findAll(any())).thenReturn(githubPermissionsMappingDtos);
-    return githubPermissionsMappingDtos;
-  }
-
-  private void bindGroupsToUser(String... groupNames) {
-    Set<GroupDto> groupDtos = Arrays.stream(groupNames)
-      .map(groupName -> new GroupDto().setName(ORGANIZATION_NAME + "/" + groupName).setUuid("uuid_" + groupName))
-      .collect(toSet());
-    when(userSession.getGroups()).thenReturn(groupDtos);
-  }
-
-  @Test
-  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<Collection<UserPermissionChange>> 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 (file)
index 6df2d2c..0000000
+++ /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 (file)
index ad989a7..0000000
+++ /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<ProjectAlmSettingDto> 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<ProjectAlmSettingDto> 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);
-  }
-
-}
index dd29147cdf82ec028e052ef47d72dd9829d70423..4553c0bc9739996acf06762e345f7923ea4a1415 100644 (file)
 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";
index b17b456e94270ce012c30c280aff204fd80e61e7..621fc056d7d023d269148fa7c1d558763413b99b 100644 (file)
@@ -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;
index bdf8daafc695dc9af65e57fa47fe3a4621a9614d..a4056d43a92cb3dcd2d15691e800d92da36bfcfc 100644 (file)
@@ -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;