diff options
author | Aurelien Poscia <aurelien.poscia@sonarsource.com> | 2022-03-14 14:28:20 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-03-17 20:03:08 +0000 |
commit | 6d87f55163859560a0c0c18f109d3d89ebf70d40 (patch) | |
tree | c2b55b464a09946a560f9e3f60891d2e99bce5ed | |
parent | fdf5523c6d9d56f2cd7cb22a9ca6dae89bd1f686 (diff) | |
download | sonarqube-6d87f55163859560a0c0c18f109d3d89ebf70d40.tar.gz sonarqube-6d87f55163859560a0c0c18f109d3d89ebf70d40.zip |
SONAR-14742 Project import from GitHub, Bitbucket and Azure can clash with existing project key
16 files changed, 352 insertions, 197 deletions
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 new file mode 100644 index 00000000000..7f2459e4345 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/ProjectKeyGenerator.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.lang.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-webapi/src/main/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectAction.java index 9441e4a8397..c44e393a92b 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectAction.java @@ -19,7 +19,6 @@ */ package org.sonar.server.almintegration.ws.azure; -import com.google.common.annotations.VisibleForTesting; import java.util.Optional; import org.sonar.alm.client.azure.AzureDevOpsHttpClient; import org.sonar.alm.client.azure.GsonAzureRepo; @@ -34,6 +33,7 @@ import org.sonar.db.alm.setting.ProjectAlmSettingDto; import org.sonar.db.component.ComponentDto; 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.component.ComponentUpdater; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.user.UserSession; @@ -57,16 +57,18 @@ public class ImportAzureProjectAction implements AlmIntegrationsWsAction { private final ProjectDefaultVisibility projectDefaultVisibility; private final ComponentUpdater componentUpdater; private final ImportHelper importHelper; + private final ProjectKeyGenerator projectKeyGenerator; public ImportAzureProjectAction(DbClient dbClient, UserSession userSession, AzureDevOpsHttpClient azureDevOpsHttpClient, ProjectDefaultVisibility projectDefaultVisibility, ComponentUpdater componentUpdater, - ImportHelper importHelper) { + ImportHelper importHelper, ProjectKeyGenerator projectKeyGenerator) { this.dbClient = dbClient; this.userSession = userSession; this.azureDevOpsHttpClient = azureDevOpsHttpClient; this.projectDefaultVisibility = projectDefaultVisibility; this.componentUpdater = componentUpdater; this.importHelper = importHelper; + this.projectKeyGenerator = projectKeyGenerator; } @Override @@ -128,8 +130,9 @@ public class ImportAzureProjectAction implements AlmIntegrationsWsAction { private ComponentDto createProject(DbSession dbSession, GsonAzureRepo repo) { boolean visibility = projectDefaultVisibility.get(dbSession).isPrivate(); + String uniqueProjectKey = projectKeyGenerator.generateUniqueProjectKey(repo.getProject().getName(), repo.getName()); return componentUpdater.createWithoutCommit(dbSession, newComponentBuilder() - .setKey(generateProjectKey(repo.getProject().getName(), repo.getName())) + .setKey(uniqueProjectKey) .setName(repo.getName()) .setPrivate(visibility) .setQualifier(PROJECT) @@ -152,15 +155,4 @@ public class ImportAzureProjectAction implements AlmIntegrationsWsAction { componentDto.name(), componentDto.getKey()); } - @VisibleForTesting - String generateProjectKey(String projectName, String repoName) { - String sqProjectKey = projectName + "_" + repoName; - - if (sqProjectKey.length() > 250) { - sqProjectKey = sqProjectKey.substring(sqProjectKey.length() - 250); - } - - return sqProjectKey.replace(" ", "_"); - } - } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java index 92eaf03522b..8a2fc032ab0 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java @@ -33,6 +33,7 @@ import org.sonar.db.alm.setting.ProjectAlmSettingDto; import org.sonar.db.component.ComponentDto; 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.component.ComponentUpdater; import org.sonar.server.component.NewComponent; import org.sonar.server.project.ProjectDefaultVisibility; @@ -56,15 +57,18 @@ public class ImportBitbucketCloudRepoAction implements AlmIntegrationsWsAction { private final ProjectDefaultVisibility projectDefaultVisibility; private final ComponentUpdater componentUpdater; private final ImportHelper importHelper; + private final ProjectKeyGenerator projectKeyGenerator; public ImportBitbucketCloudRepoAction(DbClient dbClient, UserSession userSession, BitbucketCloudRestClient bitbucketCloudRestClient, - ProjectDefaultVisibility projectDefaultVisibility, ComponentUpdater componentUpdater, ImportHelper importHelper) { + ProjectDefaultVisibility projectDefaultVisibility, ComponentUpdater componentUpdater, ImportHelper importHelper, + ProjectKeyGenerator projectKeyGenerator) { this.dbClient = dbClient; this.userSession = userSession; this.bitbucketCloudRestClient = bitbucketCloudRestClient; this.projectDefaultVisibility = projectDefaultVisibility; this.componentUpdater = componentUpdater; this.importHelper = importHelper; + this.projectKeyGenerator = projectKeyGenerator; } @Override @@ -124,8 +128,9 @@ public class ImportBitbucketCloudRepoAction implements AlmIntegrationsWsAction { private ComponentDto createProject(DbSession dbSession, String workspace, Repository repo, @Nullable String defaultBranchName) { boolean visibility = projectDefaultVisibility.get(dbSession).isPrivate(); + String uniqueProjectKey = projectKeyGenerator.generateUniqueProjectKey(workspace, repo.getSlug()); NewComponent newProject = newComponentBuilder() - .setKey(workspace + "_" + repo.getSlug()) + .setKey(uniqueProjectKey) .setName(repo.getName()) .setPrivate(visibility) .setQualifier(PROJECT) diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectAction.java index 7c2cd6be9d6..0459fdc797c 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectAction.java @@ -36,6 +36,7 @@ import org.sonar.db.alm.setting.ProjectAlmSettingDto; import org.sonar.db.component.ComponentDto; 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.component.ComponentUpdater; import org.sonar.server.component.NewComponent; import org.sonar.server.project.ProjectDefaultVisibility; @@ -60,16 +61,18 @@ public class ImportBitbucketServerProjectAction implements AlmIntegrationsWsActi private final ProjectDefaultVisibility projectDefaultVisibility; private final ComponentUpdater componentUpdater; private final ImportHelper importHelper; + private final ProjectKeyGenerator projectKeyGenerator; public ImportBitbucketServerProjectAction(DbClient dbClient, UserSession userSession, BitbucketServerRestClient bitbucketServerRestClient, ProjectDefaultVisibility projectDefaultVisibility, ComponentUpdater componentUpdater, - ImportHelper importHelper) { + ImportHelper importHelper, ProjectKeyGenerator projectKeyGenerator) { this.dbClient = dbClient; this.userSession = userSession; this.bitbucketServerRestClient = bitbucketServerRestClient; this.projectDefaultVisibility = projectDefaultVisibility; this.componentUpdater = componentUpdater; this.importHelper = importHelper; + this.projectKeyGenerator = projectKeyGenerator; } @Override @@ -141,8 +144,9 @@ public class ImportBitbucketServerProjectAction implements AlmIntegrationsWsActi private ComponentDto createProject(DbSession dbSession, Repository repo, @Nullable String defaultBranchName) { boolean visibility = projectDefaultVisibility.get(dbSession).isPrivate(); + String uniqueProjectKey = projectKeyGenerator.generateUniqueProjectKey(repo.getProject().getKey(), repo.getSlug()); NewComponent newProject = newComponentBuilder() - .setKey(getProjectKey(repo)) + .setKey(uniqueProjectKey) .setName(repo.getName()) .setPrivate(visibility) .setQualifier(PROJECT) @@ -165,9 +169,4 @@ public class ImportBitbucketServerProjectAction implements AlmIntegrationsWsActi componentDto.name(), componentDto.getKey()); } - private static String getProjectKey(Repository repo) { - String key = repo.getProject().getKey() + "_" + repo.getSlug(); - return key.replace("~", ""); - } - } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java index 796a4dcd2df..453d4e98d7d 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java @@ -35,6 +35,7 @@ import org.sonar.db.alm.setting.ProjectAlmSettingDto; import org.sonar.db.component.ComponentDto; 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.component.ComponentUpdater; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.project.ProjectDefaultVisibility; @@ -59,15 +60,17 @@ public class ImportGithubProjectAction implements AlmIntegrationsWsAction { private final GithubApplicationClient githubApplicationClient; private final ComponentUpdater componentUpdater; private final ImportHelper importHelper; + private final ProjectKeyGenerator projectKeyGenerator; public ImportGithubProjectAction(DbClient dbClient, UserSession userSession, ProjectDefaultVisibility projectDefaultVisibility, - GithubApplicationClientImpl githubApplicationClient, ComponentUpdater componentUpdater, ImportHelper importHelper) { + GithubApplicationClientImpl githubApplicationClient, ComponentUpdater componentUpdater, ImportHelper importHelper, ProjectKeyGenerator projectKeyGenerator) { this.dbClient = dbClient; this.userSession = userSession; this.projectDefaultVisibility = projectDefaultVisibility; this.githubApplicationClient = githubApplicationClient; this.componentUpdater = componentUpdater; this.importHelper = importHelper; + this.projectKeyGenerator = projectKeyGenerator; } @Override @@ -131,19 +134,16 @@ public class ImportGithubProjectAction implements AlmIntegrationsWsAction { private ComponentDto createProject(DbSession dbSession, Repository repo, String mainBranchName) { boolean visibility = projectDefaultVisibility.get(dbSession).isPrivate(); + String uniqueProjectKey = projectKeyGenerator.generateUniqueProjectKey(repo.getFullName()); return componentUpdater.createWithoutCommit(dbSession, newComponentBuilder() - .setKey(getProjectKeyFromRepository(repo)) - .setName(repo.getName()) - .setPrivate(visibility) - .setQualifier(PROJECT) - .build(), + .setKey(uniqueProjectKey) + .setName(repo.getName()) + .setPrivate(visibility) + .setQualifier(PROJECT) + .build(), userSession.getUuid(), userSession.getLogin(), mainBranchName, s -> {}); } - static String getProjectKeyFromRepository(Repository repo) { - return repo.getFullName().replace("/", "_"); - } - private void populatePRSetting(DbSession dbSession, Repository repo, ComponentDto componentDto, AlmSettingDto almSettingDto) { ProjectAlmSettingDto projectAlmSettingDto = new ProjectAlmSettingDto() .setAlmSettingUuid(almSettingDto.getUuid()) diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java index 86b366ea039..138ecb89723 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java @@ -19,7 +19,6 @@ */ package org.sonar.server.almintegration.ws.gitlab; -import com.google.common.annotations.VisibleForTesting; import java.util.Optional; import javax.annotation.Nullable; import org.sonar.alm.client.gitlab.GitLabBranch; @@ -28,7 +27,6 @@ import org.sonar.alm.client.gitlab.Project; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; -import org.sonar.core.util.UuidFactory; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.alm.pat.AlmPatDto; @@ -37,6 +35,7 @@ import org.sonar.db.alm.setting.ProjectAlmSettingDto; import org.sonar.db.component.ComponentDto; 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.component.ComponentUpdater; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.user.UserSession; @@ -56,19 +55,19 @@ public class ImportGitLabProjectAction implements AlmIntegrationsWsAction { private final ProjectDefaultVisibility projectDefaultVisibility; private final GitlabHttpClient gitlabHttpClient; private final ComponentUpdater componentUpdater; - private final UuidFactory uuidFactory; private final ImportHelper importHelper; + private final ProjectKeyGenerator projectKeyGenerator; public ImportGitLabProjectAction(DbClient dbClient, UserSession userSession, ProjectDefaultVisibility projectDefaultVisibility, GitlabHttpClient gitlabHttpClient, - ComponentUpdater componentUpdater, UuidFactory uuidFactory, ImportHelper importHelper) { + ComponentUpdater componentUpdater, ImportHelper importHelper, ProjectKeyGenerator projectKeyGenerator) { this.dbClient = dbClient; this.userSession = userSession; this.projectDefaultVisibility = projectDefaultVisibility; this.gitlabHttpClient = gitlabHttpClient; this.componentUpdater = componentUpdater; - this.uuidFactory = uuidFactory; this.importHelper = importHelper; + this.projectKeyGenerator = projectKeyGenerator; } @Override @@ -135,10 +134,10 @@ public class ImportGitLabProjectAction implements AlmIntegrationsWsAction { private ComponentDto createProject(DbSession dbSession, Project gitlabProject, @Nullable String mainBranchName) { boolean visibility = projectDefaultVisibility.get(dbSession).isPrivate(); - String sqProjectKey = generateProjectKey(gitlabProject.getPathWithNamespace(), uuidFactory.create()); + String uniqueProjectKey = projectKeyGenerator.generateUniqueProjectKey(gitlabProject.getPathWithNamespace()); return componentUpdater.createWithoutCommit(dbSession, newComponentBuilder() - .setKey(sqProjectKey) + .setKey(uniqueProjectKey) .setName(gitlabProject.getName()) .setPrivate(visibility) .setQualifier(PROJECT) @@ -147,14 +146,4 @@ public class ImportGitLabProjectAction implements AlmIntegrationsWsAction { }); } - @VisibleForTesting - String generateProjectKey(String pathWithNamespace, String uuid) { - String sqProjectKey = pathWithNamespace + "_" + uuid; - - if (sqProjectKey.length() > 250) { - sqProjectKey = sqProjectKey.substring(sqProjectKey.length() - 250); - } - - return sqProjectKey.replace("/", "_"); - } } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/ImportHelperTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/ImportHelperTest.java index 3ea30f971df..828be41dc10 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/ImportHelperTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/ImportHelperTest.java @@ -24,8 +24,6 @@ import org.junit.Test; import org.sonar.api.server.ws.Request; import org.sonar.api.utils.System2; import org.sonar.db.DbTester; -import org.sonar.db.component.BranchDto; -import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentTesting; import org.sonar.server.exceptions.NotFoundException; @@ -42,11 +40,6 @@ public class ImportHelperTest { private final System2 system2 = System2.INSTANCE; private final ComponentDto componentDto = ComponentTesting.newPublicProjectDto(); - private final BranchDto branchDto = new BranchDto() - .setBranchType(BranchType.BRANCH) - .setKey("main") - .setUuid(componentDto.uuid()) - .setProjectUuid(componentDto.uuid()); private final Request request = mock(Request.class); @Rule @@ -55,7 +48,7 @@ public class ImportHelperTest { @Rule public UserSessionRule userSession = UserSessionRule.standalone(); - private ImportHelper underTest = new ImportHelper(db.getDbClient(), userSession); + private final ImportHelper underTest = new ImportHelper(db.getDbClient(), userSession); @Test public void it_throws_exception_when_provisioning_project_without_permission() { @@ -91,7 +84,7 @@ public class ImportHelperTest { CreateWsResponse.Project project = response.getProject(); assertThat(project).extracting(CreateWsResponse.Project::getKey, CreateWsResponse.Project::getName, - CreateWsResponse.Project::getQualifier) + CreateWsResponse.Project::getQualifier) .containsExactly(componentDto.getDbKey(), componentDto.name(), componentDto.qualifier()); } } 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 new file mode 100644 index 00000000000..c8e6c628fc6 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/ProjectKeyGeneratorTest.java @@ -0,0 +1,92 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.lang.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/almintegration/ws/azure/ImportAzureProjectActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectActionTest.java index 424c9e00e6f..6b5d25f5ca8 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectActionTest.java @@ -20,8 +20,6 @@ package org.sonar.server.almintegration.ws.azure; import java.util.Optional; -import java.util.stream.IntStream; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -41,6 +39,7 @@ import org.sonar.db.component.BranchDto; 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.es.TestProjectIndexers; import org.sonar.server.exceptions.BadRequestException; @@ -56,12 +55,12 @@ import org.sonar.server.ws.TestRequest; import org.sonar.server.ws.WsActionTester; import org.sonarqube.ws.Projects; -import static java.util.stream.Collectors.joining; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.tuple; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.sonar.db.alm.integration.pat.AlmPatsTesting.newAlmPatDto; import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; @@ -69,6 +68,8 @@ import static org.sonar.db.permission.GlobalPermission.SCAN; public class ImportAzureProjectActionTest { + private static final String GENERATED_PROJECT_KEY = "TEST_PROJECT_KEY"; + @Rule public UserSessionRule userSession = UserSessionRule.standalone(); @Rule @@ -82,13 +83,15 @@ public class ImportAzureProjectActionTest { private final Encryption encryption = mock(Encryption.class); private final ImportHelper importHelper = new ImportHelper(db.getDbClient(), userSession); private final ProjectDefaultVisibility projectDefaultVisibility = mock(ProjectDefaultVisibility.class); + private final ProjectKeyGenerator projectKeyGenerator = mock(ProjectKeyGenerator.class); private final ImportAzureProjectAction importAzureProjectAction = new ImportAzureProjectAction(db.getDbClient(), userSession, - azureDevOpsHttpClient, projectDefaultVisibility, componentUpdater, importHelper); + azureDevOpsHttpClient, projectDefaultVisibility, componentUpdater, importHelper, projectKeyGenerator); private final WsActionTester ws = new WsActionTester(importAzureProjectAction); @Before public void before() { when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE); + when(projectKeyGenerator.generateUniqueProjectKey(any(), any())).thenReturn(GENERATED_PROJECT_KEY); } @Test @@ -113,7 +116,7 @@ public class ImportAzureProjectActionTest { .executeProtobuf(Projects.CreateWsResponse.class); Projects.CreateWsResponse.Project result = response.getProject(); - assertThat(result.getKey()).isEqualTo(repo.getProject().getName() + "_" + repo.getName()); + assertThat(result.getKey()).isEqualTo(GENERATED_PROJECT_KEY); assertThat(result.getName()).isEqualTo(repo.getName()); Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); @@ -132,6 +135,8 @@ public class ImportAzureProjectActionTest { .findFirst(); assertThat(mainBranch).isPresent(); assertThat(mainBranch.get().getKey()).hasToString("repo-default-branch"); + + verify(projectKeyGenerator).generateUniqueProjectKey(repo.getProject().getName(), repo.getName()); } @Test @@ -236,8 +241,7 @@ public class ImportAzureProjectActionTest { dto.setUserUuid(user.getUuid()); }); GsonAzureRepo repo = getGsonAzureRepo(); - String projectKey = repo.getProject().getName() + "_" + repo.getName(); - db.components().insertPublicProject(p -> p.setDbKey(projectKey)); + db.components().insertPublicProject(p -> p.setDbKey(GENERATED_PROJECT_KEY)); when(azureDevOpsHttpClient.getRepo(almSetting.getUrl(), almSetting.getDecryptedPersonalAccessToken(encryption), "project-name", "repo-name")).thenReturn(repo); @@ -248,21 +252,7 @@ public class ImportAzureProjectActionTest { assertThatThrownBy(request::execute) .isInstanceOf(BadRequestException.class) - .hasMessage("Could not create null, key already exists: " + projectKey); - } - - @Test - public void sanitize_project_and_repo_names_with_invalid_characters() { - assertThat(importAzureProjectAction.generateProjectKey("project name", "repo name")) - .isEqualTo("project_name_repo_name"); - } - - @Test - public void sanitize_long_project_and_repo_names() { - String projectName = IntStream.range(0, 260).mapToObj(i -> "a").collect(joining()); - - assertThat(importAzureProjectAction.generateProjectKey(projectName, "repo name")) - .hasSize(250); + .hasMessage("Could not create null, key already exists: " + GENERATED_PROJECT_KEY); } @Test diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionTest.java index da4c6cfbe7f..6e50410f731 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionTest.java @@ -39,6 +39,7 @@ import org.sonar.db.component.BranchDto; 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.es.TestProjectIndexers; import org.sonar.server.exceptions.BadRequestException; @@ -54,17 +55,22 @@ import org.sonar.server.ws.TestRequest; import org.sonar.server.ws.WsActionTester; import org.sonarqube.ws.Projects; +import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.tuple; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.sonar.db.alm.integration.pat.AlmPatsTesting.newAlmPatDto; import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; import static org.sonar.db.permission.GlobalPermission.SCAN; public class ImportBitbucketCloudRepoActionTest { + + private static final String GENERATED_PROJECT_KEY = "TEST_PROJECT_KEY"; + @Rule public UserSessionRule userSession = UserSessionRule.standalone(); @Rule @@ -77,12 +83,14 @@ public class ImportBitbucketCloudRepoActionTest { mock(PermissionTemplateService.class), new FavoriteUpdater(db.getDbClient()), new TestProjectIndexers(), new SequenceUuidFactory()); private final ImportHelper importHelper = new ImportHelper(db.getDbClient(), userSession); + private final ProjectKeyGenerator projectKeyGenerator = mock(ProjectKeyGenerator.class); private final WsActionTester ws = new WsActionTester(new ImportBitbucketCloudRepoAction(db.getDbClient(), userSession, - bitbucketCloudRestClient, projectDefaultVisibility, componentUpdater, importHelper)); + bitbucketCloudRestClient, projectDefaultVisibility, componentUpdater, importHelper, projectKeyGenerator)); @Before public void before() { when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE); + when(projectKeyGenerator.generateUniqueProjectKey(any(), any())).thenReturn(GENERATED_PROJECT_KEY); } @Test @@ -103,7 +111,7 @@ public class ImportBitbucketCloudRepoActionTest { .executeProtobuf(Projects.CreateWsResponse.class); Projects.CreateWsResponse.Project result = response.getProject(); - assertThat(result.getKey()).isEqualTo(almSetting.getAppId() + "_" + repo.getSlug()); + assertThat(result.getKey()).isEqualTo(GENERATED_PROJECT_KEY); assertThat(result.getName()).isEqualTo(repo.getName()); Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); @@ -115,6 +123,7 @@ public class ImportBitbucketCloudRepoActionTest { Optional<BranchDto> branchDto = db.getDbClient().branchDao().selectByBranchKey(db.getSession(), projectDto.get().getUuid(), "develop"); assertThat(branchDto).isPresent(); assertThat(branchDto.get().isMain()).isTrue(); + verify(projectKeyGenerator).generateUniqueProjectKey(requireNonNull(almSetting.getAppId()), repo.getSlug()); } @Test @@ -127,8 +136,7 @@ public class ImportBitbucketCloudRepoActionTest { dto.setUserUuid(user.getUuid()); }); Repository repo = getGsonBBCRepo(); - String projectKey = almSetting.getAppId() + "_" + repo.getSlug(); - db.components().insertPublicProject(p -> p.setDbKey(projectKey)); + db.components().insertPublicProject(p -> p.setDbKey(GENERATED_PROJECT_KEY)); when(bitbucketCloudRestClient.getRepo(any(), any(), any())).thenReturn(repo); @@ -138,7 +146,7 @@ public class ImportBitbucketCloudRepoActionTest { assertThatThrownBy(request::execute) .isInstanceOf(BadRequestException.class) - .hasMessageContaining("Could not create null, key already exists: " + projectKey); + .hasMessageContaining("Could not create null, key already exists: " + GENERATED_PROJECT_KEY); } @Test diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionTest.java index f1cc0ca6dde..ea7af30e4e8 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionTest.java @@ -44,6 +44,7 @@ import org.sonar.db.component.BranchDto; 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.es.TestProjectIndexers; import org.sonar.server.exceptions.BadRequestException; @@ -58,6 +59,7 @@ import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.WsActionTester; import org.sonarqube.ws.Projects; +import static java.util.Objects.requireNonNull; import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; import static org.apache.commons.lang.math.JVMRandom.nextLong; import static org.assertj.core.api.Assertions.assertThat; @@ -65,12 +67,14 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.tuple; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.sonar.db.alm.integration.pat.AlmPatsTesting.newAlmPatDto; import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; import static org.sonar.db.permission.GlobalPermission.SCAN; public class ImportBitbucketServerProjectActionTest { + private static final String GENERATED_PROJECT_KEY = "TEST_PROJECT_KEY"; @Rule public UserSessionRule userSession = UserSessionRule.standalone(); @@ -84,8 +88,9 @@ public class ImportBitbucketServerProjectActionTest { mock(PermissionTemplateService.class), new FavoriteUpdater(db.getDbClient()), new TestProjectIndexers(), new SequenceUuidFactory()); private final ImportHelper importHelper = new ImportHelper(db.getDbClient(), userSession); + private final ProjectKeyGenerator projectKeyGenerator = mock(ProjectKeyGenerator.class); private final WsActionTester ws = new WsActionTester(new ImportBitbucketServerProjectAction(db.getDbClient(), userSession, - bitbucketServerRestClient, projectDefaultVisibility, componentUpdater, importHelper)); + bitbucketServerRestClient, projectDefaultVisibility, componentUpdater, importHelper, projectKeyGenerator)); private static BranchesList defaultBranchesList; @@ -98,6 +103,7 @@ public class ImportBitbucketServerProjectActionTest { @Before public void before() { when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE); + when(projectKeyGenerator.generateUniqueProjectKey(any(), any())).thenReturn(GENERATED_PROJECT_KEY); } @Test @@ -121,40 +127,13 @@ public class ImportBitbucketServerProjectActionTest { .executeProtobuf(Projects.CreateWsResponse.class); Projects.CreateWsResponse.Project result = response.getProject(); - assertThat(result.getKey()).isEqualTo(project.getKey() + "_" + repo.getSlug()); + assertThat(result.getKey()).isEqualTo(GENERATED_PROJECT_KEY); assertThat(result.getName()).isEqualTo(repo.getName()); Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); assertThat(projectDto).isPresent(); assertThat(db.getDbClient().projectAlmSettingDao().selectByProject(db.getSession(), projectDto.get())).isPresent(); - } - - @Test - public void import_project_with_tilda() { - UserDto user = db.users().insertUser(); - userSession.logIn(user).addPermission(PROVISION_PROJECTS); - AlmSettingDto almSetting = db.almSettings().insertGitHubAlmSetting(); - db.almPats().insert(dto -> { - dto.setAlmSettingUuid(almSetting.getUuid()); - dto.setUserUuid(user.getUuid()); - }); - Project project = getGsonBBSProject(); - project.setKey("~" + project.getKey()); - Repository repo = getGsonBBSRepo(project); - when(bitbucketServerRestClient.getRepo(any(), any(), any(), any())).thenReturn(repo); - when(bitbucketServerRestClient.getBranches(any(), any(), any(), any())).thenReturn(defaultBranchesList); - - Projects.CreateWsResponse response = ws.newRequest() - .setParam("almSetting", almSetting.getKey()) - .setParam("projectKey", "~projectKey") - .setParam("repositorySlug", "repo-slug") - .executeProtobuf(Projects.CreateWsResponse.class); - - Projects.CreateWsResponse.Project result = response.getProject(); - - String key = project.getKey() + "_" + repo.getSlug(); - assertThat(result.getKey()).isNotEqualTo(key); - assertThat(result.getKey()).isEqualTo(key.substring(1)); + verify(projectKeyGenerator).generateUniqueProjectKey(requireNonNull(project.getKey()), repo.getSlug()); } @Test @@ -168,8 +147,7 @@ public class ImportBitbucketServerProjectActionTest { }); Project project = getGsonBBSProject(); Repository repo = getGsonBBSRepo(project); - String projectKey = project.getKey() + "_" + repo.getSlug(); - db.components().insertPublicProject(p -> p.setDbKey(projectKey)); + db.components().insertPublicProject(p -> p.setDbKey(GENERATED_PROJECT_KEY)); assertThatThrownBy(() -> { when(bitbucketServerRestClient.getRepo(any(), any(), any(), any())).thenReturn(repo); @@ -182,7 +160,7 @@ public class ImportBitbucketServerProjectActionTest { .execute(); }) .isInstanceOf(BadRequestException.class) - .hasMessage("Could not create null, key already exists: " + projectKey); + .hasMessage("Could not create null, key already exists: " + GENERATED_PROJECT_KEY); } @Test diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionTest.java index 3f95e76a037..df54db59f9c 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionTest.java @@ -36,9 +36,9 @@ import org.sonar.db.permission.GlobalPermission; 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.es.TestProjectIndexers; -import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.favorite.FavoriteUpdater; @@ -63,6 +63,8 @@ import static org.sonar.server.tester.UserSessionRule.standalone; public class ImportGithubProjectActionTest { + private static final String PROJECT_KEY_NAME = "PROJECT_NAME"; + @Rule public UserSessionRule userSession = standalone(); @@ -76,9 +78,10 @@ public class ImportGithubProjectActionTest { mock(PermissionTemplateService.class), new FavoriteUpdater(db.getDbClient()), new TestProjectIndexers(), new SequenceUuidFactory()); private final ImportHelper importHelper = new ImportHelper(db.getDbClient(), userSession); + private final ProjectKeyGenerator projectKeyGenerator = mock(ProjectKeyGenerator.class); private final ProjectDefaultVisibility projectDefaultVisibility = mock(ProjectDefaultVisibility.class); private final WsActionTester ws = new WsActionTester(new ImportGithubProjectAction(db.getDbClient(), userSession, - projectDefaultVisibility, appClient, componentUpdater, importHelper)); + projectDefaultVisibility, appClient, componentUpdater, importHelper, projectKeyGenerator)); @Before public void before() { @@ -86,23 +89,23 @@ public class ImportGithubProjectActionTest { } @Test - public void import_project() { + public void importProject_ifProjectWithSameNameDoesNotExist_importSucceed() { AlmSettingDto githubAlmSetting = setupAlm(); db.almPats().insert(p -> p.setAlmSettingUuid(githubAlmSetting.getUuid()).setUserUuid(userSession.getUuid())); - GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, "Hello-World", false, "octocat/Hello-World", - "https://github.sonarsource.com/api/v3/repos/octocat/Hello-World", "default-branch"); - when(appClient.getRepository(any(), any(), any(), any())) - .thenReturn(Optional.of(repository)); + GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false, "octocat/" + PROJECT_KEY_NAME, + "https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, "default-branch"); + when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository)); + when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME); Projects.CreateWsResponse response = ws.newRequest() .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey()) .setParam(PARAM_ORGANIZATION, "octocat") - .setParam(PARAM_REPOSITORY_KEY, "octocat/Hello-World") + .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME) .executeProtobuf(Projects.CreateWsResponse.class); Projects.CreateWsResponse.Project result = response.getProject(); - assertThat(result.getKey()).isEqualTo(repository.getFullName().replace("/", "_")); + assertThat(result.getKey()).isEqualTo(PROJECT_KEY_NAME); assertThat(result.getName()).isEqualTo(repository.getName()); Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); @@ -114,33 +117,35 @@ public class ImportGithubProjectActionTest { } @Test - public void fail_project_already_exist() { + public void importProject_ifProjectWithSameNameAlreadyExists_importSucceed() { AlmSettingDto githubAlmSetting = setupAlm(); db.almPats().insert(p -> p.setAlmSettingUuid(githubAlmSetting.getUuid()).setUserUuid(userSession.getUuid())); - db.components().insertPublicProject(p -> p.setDbKey("octocat_Hello-World")); + db.components().insertPublicProject(p -> p.setDbKey("Hello-World")); - GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, "Hello-World", false, "octocat/Hello-World", + GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, "Hello-World", false, "Hello-World", "https://github.sonarsource.com/api/v3/repos/octocat/Hello-World", "main"); - when(appClient.getRepository(any(), any(), any(), any())) - .thenReturn(Optional.of(repository)); + when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository)); + when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME); - TestRequest request = ws.newRequest() - .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey()) - .setParam(PARAM_ORGANIZATION, "octocat") - .setParam(PARAM_REPOSITORY_KEY, "octocat/Hello-World"); - assertThatThrownBy(request::execute) - .isInstanceOf(BadRequestException.class) - .hasMessage("Could not create null, key already exists: octocat_Hello-World"); + Projects.CreateWsResponse response = ws.newRequest() + .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey()) + .setParam(PARAM_ORGANIZATION, "octocat") + .setParam(PARAM_REPOSITORY_KEY, "Hello-World") + .executeProtobuf(Projects.CreateWsResponse.class); + + Projects.CreateWsResponse.Project result = response.getProject(); + assertThat(result.getKey()).isEqualTo(PROJECT_KEY_NAME); + assertThat(result.getName()).isEqualTo(repository.getName()); } @Test public void fail_when_not_logged_in() { TestRequest request = ws.newRequest() - .setParam(PARAM_ALM_SETTING, "asdfghjkl") - .setParam(PARAM_ORGANIZATION, "test") - .setParam(PARAM_REPOSITORY_KEY, "test/repo"); + .setParam(PARAM_ALM_SETTING, "asdfghjkl") + .setParam(PARAM_ORGANIZATION, "test") + .setParam(PARAM_REPOSITORY_KEY, "test/repo"); assertThatThrownBy(request::execute) - .isInstanceOf(UnauthorizedException.class); + .isInstanceOf(UnauthorizedException.class); } @Test @@ -156,12 +161,12 @@ public class ImportGithubProjectActionTest { userSession.logIn(user).addPermission(GlobalPermission.PROVISION_PROJECTS); TestRequest request = ws.newRequest() - .setParam(PARAM_ALM_SETTING, "unknown") - .setParam(PARAM_ORGANIZATION, "test") - .setParam(PARAM_REPOSITORY_KEY, "test/repo"); + .setParam(PARAM_ALM_SETTING, "unknown") + .setParam(PARAM_ORGANIZATION, "test") + .setParam(PARAM_REPOSITORY_KEY, "test/repo"); assertThatThrownBy(request::execute) - .isInstanceOf(NotFoundException.class) - .hasMessage("ALM Setting 'unknown' not found"); + .isInstanceOf(NotFoundException.class) + .hasMessage("ALM Setting 'unknown' not found"); } @Test @@ -169,12 +174,12 @@ public class ImportGithubProjectActionTest { AlmSettingDto githubAlmSetting = setupAlm(); TestRequest request = ws.newRequest() - .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey()) - .setParam(PARAM_ORGANIZATION, "test") - .setParam(PARAM_REPOSITORY_KEY, "test/repo"); + .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey()) + .setParam(PARAM_ORGANIZATION, "test") + .setParam(PARAM_REPOSITORY_KEY, "test/repo"); assertThatThrownBy(request::execute) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("No personal access token found"); + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("No personal access token found"); } @Test diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionTest.java index 62487cca25b..fa1be6a1219 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionTest.java @@ -20,7 +20,6 @@ package org.sonar.server.almintegration.ws.gitlab; import java.util.Optional; -import java.util.stream.IntStream; import org.assertj.core.api.Assertions; import org.junit.Before; import org.junit.Rule; @@ -31,13 +30,13 @@ import org.sonar.alm.client.gitlab.Project; import org.sonar.api.utils.System2; import org.sonar.core.i18n.I18n; import org.sonar.core.util.SequenceUuidFactory; -import org.sonar.core.util.UuidFactory; import org.sonar.db.DbTester; import org.sonar.db.alm.setting.AlmSettingDto; import org.sonar.db.component.BranchDto; 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.es.TestProjectIndexers; import org.sonar.server.favorite.FavoriteUpdater; @@ -50,7 +49,6 @@ import org.sonarqube.ws.Projects; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.joining; import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; @@ -63,6 +61,8 @@ import static org.sonar.server.tester.UserSessionRule.standalone; public class ImportGitLabProjectActionTest { + private static final String PROJECT_KEY_NAME = "PROJECT_NAME"; + private final System2 system2 = mock(System2.class); @Rule @@ -75,11 +75,11 @@ public class ImportGitLabProjectActionTest { mock(PermissionTemplateService.class), new FavoriteUpdater(db.getDbClient()), new TestProjectIndexers(), new SequenceUuidFactory()); private final GitlabHttpClient gitlabHttpClient = mock(GitlabHttpClient.class); - private final UuidFactory uuidFactory = mock(UuidFactory.class); private final ImportHelper importHelper = new ImportHelper(db.getDbClient(), userSession); private final ProjectDefaultVisibility projectDefaultVisibility = mock(ProjectDefaultVisibility.class); + private final ProjectKeyGenerator projectKeyGenerator = mock(ProjectKeyGenerator.class); private final ImportGitLabProjectAction importGitLabProjectAction = new ImportGitLabProjectAction( - db.getDbClient(), userSession, projectDefaultVisibility, gitlabHttpClient, componentUpdater, uuidFactory, importHelper); + db.getDbClient(), userSession, projectDefaultVisibility, gitlabHttpClient, componentUpdater, importHelper, projectKeyGenerator); private final WsActionTester ws = new WsActionTester(importGitLabProjectAction); @Before @@ -100,7 +100,7 @@ public class ImportGitLabProjectActionTest { Project project = getGitlabProject(); when(gitlabHttpClient.getProject(any(), any(), any())).thenReturn(project); when(gitlabHttpClient.getBranches(any(), any(), any())).thenReturn(singletonList(new GitLabBranch("master", true))); - when(uuidFactory.create()).thenReturn("uuid"); + when(projectKeyGenerator.generateUniqueProjectKey(project.getPathWithNamespace())).thenReturn(PROJECT_KEY_NAME); Projects.CreateWsResponse response = ws.newRequest() .setParam("almSetting", almSetting.getKey()) @@ -110,7 +110,7 @@ public class ImportGitLabProjectActionTest { verify(gitlabHttpClient).getProject(almSetting.getUrl(), "PAT", 12345L); Projects.CreateWsResponse.Project result = response.getProject(); - assertThat(result.getKey()).isEqualTo(project.getPathWithNamespace() + "_uuid"); + assertThat(result.getKey()).isEqualTo(PROJECT_KEY_NAME); assertThat(result.getName()).isEqualTo(project.getName()); Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); @@ -131,7 +131,7 @@ public class ImportGitLabProjectActionTest { Project project = getGitlabProject(); when(gitlabHttpClient.getProject(any(), any(), any())).thenReturn(project); when(gitlabHttpClient.getBranches(any(), any(), any())).thenReturn(singletonList(new GitLabBranch("main", true))); - when(uuidFactory.create()).thenReturn("uuid"); + when(projectKeyGenerator.generateUniqueProjectKey(project.getPathWithNamespace())).thenReturn(PROJECT_KEY_NAME); Projects.CreateWsResponse response = ws.newRequest() .setParam("almSetting", almSetting.getKey()) @@ -142,7 +142,7 @@ public class ImportGitLabProjectActionTest { verify(gitlabHttpClient).getBranches(almSetting.getUrl(), "PAT", 12345L); Projects.CreateWsResponse.Project result = response.getProject(); - assertThat(result.getKey()).isEqualTo(project.getPathWithNamespace() + "_uuid"); + assertThat(result.getKey()).isEqualTo(PROJECT_KEY_NAME); assertThat(result.getName()).isEqualTo(project.getName()); Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); @@ -167,7 +167,7 @@ public class ImportGitLabProjectActionTest { Project project = getGitlabProject(); when(gitlabHttpClient.getProject(any(), any(), any())).thenReturn(project); when(gitlabHttpClient.getBranches(any(), any(), any())).thenReturn(emptyList()); - when(uuidFactory.create()).thenReturn("uuid"); + when(projectKeyGenerator.generateUniqueProjectKey(project.getPathWithNamespace())).thenReturn(PROJECT_KEY_NAME); Projects.CreateWsResponse response = ws.newRequest() .setParam("almSetting", almSetting.getKey()) @@ -178,7 +178,7 @@ public class ImportGitLabProjectActionTest { verify(gitlabHttpClient).getBranches(almSetting.getUrl(), "PAT", 12345L); Projects.CreateWsResponse.Project result = response.getProject(); - assertThat(result.getKey()).isEqualTo(project.getPathWithNamespace() + "_uuid"); + assertThat(result.getKey()).isEqualTo(PROJECT_KEY_NAME); assertThat(result.getName()).isEqualTo(project.getName()); Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); @@ -190,36 +190,6 @@ public class ImportGitLabProjectActionTest { .containsExactlyInAnyOrder(tuple("master", true)); } - @Test - public void generate_project_key_less_than_250() { - String name = "abcdeert"; - assertThat(importGitLabProjectAction.generateProjectKey(name, "uuid")).isEqualTo("abcdeert_uuid"); - } - - @Test - public void generate_project_key_equal_250() { - String name = IntStream.range(0, 245).mapToObj(i -> "a").collect(joining()); - String projectKey = importGitLabProjectAction.generateProjectKey(name, "uuid"); - assertThat(projectKey) - .hasSize(250) - .isEqualTo(name + "_uuid"); - - } - - @Test - public void generate_project_key_more_than_250() { - String name = IntStream.range(0, 250).mapToObj(i -> "a").collect(joining()); - String projectKey = importGitLabProjectAction.generateProjectKey(name, "uuid"); - assertThat(projectKey) - .hasSize(250) - .isEqualTo(name.substring(5) + "_uuid"); - } - - @Test - public void generate_project_key_containing_slash() { - String name = "a/b/c"; - assertThat(importGitLabProjectAction.generateProjectKey(name, "uuid")).isEqualTo("a_b_c_uuid"); - } private Project getGitlabProject() { return new Project(randomAlphanumeric(5), randomAlphanumeric(5)); diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index dc63789074f..cded3afbfc0 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -56,6 +56,7 @@ 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.almsettings.MultipleAlmFeatureProvider; import org.sonar.server.almsettings.ws.AlmSettingsWsModule; import org.sonar.server.authentication.AuthenticationModule; @@ -526,6 +527,7 @@ public class PlatformLevel4 extends PlatformLevel { TimeoutConfigurationImpl.class, CredentialsEncoderHelper.class, ImportHelper.class, + ProjectKeyGenerator.class, GithubAppSecurityImpl.class, GithubApplicationClientImpl.class, GithubApplicationHttpClientImpl.class, diff --git a/sonar-core/src/main/java/org/sonar/core/component/ComponentKeys.java b/sonar-core/src/main/java/org/sonar/core/component/ComponentKeys.java index ab786773ea9..73dc84dacc7 100644 --- a/sonar-core/src/main/java/org/sonar/core/component/ComponentKeys.java +++ b/sonar-core/src/main/java/org/sonar/core/component/ComponentKeys.java @@ -34,11 +34,19 @@ public final class ComponentKeys { public static final String MALFORMED_KEY_MESSAGE = "Malformed key for '%s'. %s."; /** - * Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit + * Allowed characters are alphanumeric, '-', '_', '.' and ':' */ - private static final Pattern VALID_PROJECT_KEY_REGEXP = Pattern.compile("[\\p{Alnum}\\-_.:]*[\\p{Alpha}\\-_.:]+[\\p{Alnum}\\-_.:]*"); + private static final String VALID_PROJECT_KEY_CHARS = "\\p{Alnum}-_.:"; + + private static final Pattern INVALID_PROJECT_KEY_REGEXP = Pattern.compile("[^" + VALID_PROJECT_KEY_CHARS + "]"); + + /** + * At least one non-digit is necessary + */ + private static final Pattern VALID_PROJECT_KEY_REGEXP = Pattern.compile("[" + VALID_PROJECT_KEY_CHARS + "]*[\\p{Alpha}\\-_.:]+[" + VALID_PROJECT_KEY_CHARS + "]*"); private static final String KEY_WITH_BRANCH_FORMAT = "%s:%s"; + private static final String REPLACEMENT_CHARACTER = "_"; private ComponentKeys() { // only static stuff @@ -66,6 +74,10 @@ public final class ComponentKeys { checkArgument(isValidProjectKey(keyCandidate), MALFORMED_KEY_MESSAGE, keyCandidate, ALLOWED_CHARACTERS_MESSAGE); } + public static String sanitizeProjectKey(String rawProjectKey) { + return INVALID_PROJECT_KEY_REGEXP.matcher(rawProjectKey).replaceAll(REPLACEMENT_CHARACTER); + } + /** * Return the project key with potential branch */ diff --git a/sonar-core/src/test/java/org/sonar/core/component/ComponentKeysSanitizationTest.java b/sonar-core/src/test/java/org/sonar/core/component/ComponentKeysSanitizationTest.java new file mode 100644 index 00000000000..e8699f432ab --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/component/ComponentKeysSanitizationTest.java @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.core.component; + +import java.util.Arrays; +import java.util.Collection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(Parameterized.class) +public class ComponentKeysSanitizationTest { + + @Parameterized.Parameters(name = "{index}: input {0}, expected output {1}") + public static Collection<String[]> data() { + return Arrays.asList(new String[][] { + {"/a/b/c/", "_a_b_c_"}, + {".a.b:c:", ".a.b:c:"}, + {"_1_2_3_", "_1_2_3_"}, + {"fully_valid_-name2", "fully_valid_-name2"}, + {"°+\"*ç%&\\/()=?`^“#Ç[]|{}≠¿ ~", "___________________________"}, + }); + } + + private final String inputString; + private final String expectedOutputString; + + public ComponentKeysSanitizationTest(String inputString, String expectedOutputString) { + this.inputString = inputString; + this.expectedOutputString = expectedOutputString; + } + + @Test + public void sanitizeProjectKey() { + assertThat(ComponentKeys.sanitizeProjectKey(inputString)).isEqualTo(expectedOutputString); + } + +} |