Browse Source

SONAR-14742 Project import from GitHub, Bitbucket and Azure can clash with existing project key

tags/9.4.0.54424
Aurelien Poscia 2 years ago
parent
commit
6d87f55163
16 changed files with 352 additions and 197 deletions
  1. 63
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/ProjectKeyGenerator.java
  2. 6
    14
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectAction.java
  3. 7
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java
  4. 6
    7
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectAction.java
  5. 10
    10
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java
  6. 6
    17
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java
  7. 2
    9
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/ImportHelperTest.java
  8. 92
    0
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/ProjectKeyGeneratorTest.java
  9. 12
    22
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectActionTest.java
  10. 13
    5
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionTest.java
  11. 11
    33
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionTest.java
  12. 40
    35
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionTest.java
  13. 11
    41
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionTest.java
  14. 2
    0
      server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
  15. 14
    2
      sonar-core/src/main/java/org/sonar/core/component/ComponentKeys.java
  16. 57
    0
      sonar-core/src/test/java/org/sonar/core/component/ComponentKeysSanitizationTest.java

+ 63
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/ProjectKeyGenerator.java View File

@@ -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;
}

}

+ 6
- 14
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectAction.java View File

@@ -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(" ", "_");
}

}

+ 7
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoAction.java View File

@@ -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)

+ 6
- 7
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectAction.java View File

@@ -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("~", "");
}

}

+ 10
- 10
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java View File

@@ -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())

+ 6
- 17
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectAction.java View File

@@ -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("/", "_");
}
}

+ 2
- 9
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/ImportHelperTest.java View File

@@ -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());
}
}

+ 92
- 0
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/ProjectKeyGeneratorTest.java View File

@@ -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;
}
}

+ 12
- 22
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectActionTest.java View File

@@ -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

+ 13
- 5
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionTest.java View File

@@ -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

+ 11
- 33
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionTest.java View File

@@ -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

+ 40
- 35
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionTest.java View File

@@ -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

+ 11
- 41
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionTest.java View File

@@ -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));

+ 2
- 0
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java View File

@@ -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,

+ 14
- 2
sonar-core/src/main/java/org/sonar/core/component/ComponentKeys.java View File

@@ -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
*/

+ 57
- 0
sonar-core/src/test/java/org/sonar/core/component/ComponentKeysSanitizationTest.java View File

@@ -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);
}

}

Loading…
Cancel
Save