Browse Source

SONAR-13160 Fix display of Portfolio Admin page when project contains UTF-8 characters

tags/8.3.0.34182
Julien Lancelot 4 years ago
parent
commit
ec384a1e69
14 changed files with 202 additions and 113 deletions
  1. 26
    14
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ValidateProjectStep.java
  2. 66
    40
      server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ValidateProjectStepTest.java
  3. 1
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java
  4. 3
    4
      server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java
  5. 2
    7
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentService.java
  6. 2
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java
  7. 6
    7
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ComponentServiceUpdateKeyTest.java
  8. 5
    5
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java
  9. 5
    4
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/BulkUpdateKeyActionTest.java
  10. 3
    3
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/CreateActionTest.java
  11. 9
    14
      sonar-core/src/main/java/org/sonar/core/component/ComponentKeys.java
  12. 25
    4
      sonar-core/src/test/java/org/sonar/core/component/ComponentKeysTest.java
  13. 14
    5
      sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectReactorValidator.java
  14. 35
    4
      sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectReactorValidatorTest.java

+ 26
- 14
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ValidateProjectStep.java View File

import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.sonar.api.utils.MessageException; import org.sonar.api.utils.MessageException;
import org.sonar.api.utils.System2;
import org.sonar.ce.task.log.CeTaskMessages;
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.component.Component;
import org.sonar.ce.task.projectanalysis.component.ComponentVisitor; import org.sonar.ce.task.projectanalysis.component.ComponentVisitor;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static java.lang.String.format; import static java.lang.String.format;
import static org.sonar.api.utils.DateUtils.formatDateTime; import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonar.core.component.ComponentKeys.ALLOWED_CHARACTERS_MESSAGE;
import static org.sonar.core.component.ComponentKeys.isValidProjectKey;


/**
* Validate project and modules. It will fail in the following cases :
* <ol>
* <li>module key is not valid</li>
* <li>module key already exists in another project (same module key cannot exists in different projects)</li>
* <li>module key is already used as a project key</li>
* <li>date of the analysis is before last analysis</li>
* <li>PR targets a branch that still contains modules</li>
* </ol>
*/
public class ValidateProjectStep implements ComputationStep { public class ValidateProjectStep implements ComputationStep {


private static final Joiner MESSAGES_JOINER = Joiner.on("\n o "); private static final Joiner MESSAGES_JOINER = Joiner.on("\n o ");
private final DbClient dbClient; private final DbClient dbClient;
private final TreeRootHolder treeRootHolder; private final TreeRootHolder treeRootHolder;
private final AnalysisMetadataHolder analysisMetadataHolder; private final AnalysisMetadataHolder analysisMetadataHolder;
private final CeTaskMessages taskMessages;
private final System2 system2;


public ValidateProjectStep(DbClient dbClient, TreeRootHolder treeRootHolder, AnalysisMetadataHolder analysisMetadataHolder) {
public ValidateProjectStep(DbClient dbClient, TreeRootHolder treeRootHolder, AnalysisMetadataHolder analysisMetadataHolder, CeTaskMessages taskMessages, System2 system2) {
this.dbClient = dbClient; this.dbClient = dbClient;
this.treeRootHolder = treeRootHolder; this.treeRootHolder = treeRootHolder;
this.analysisMetadataHolder = analysisMetadataHolder; this.analysisMetadataHolder = analysisMetadataHolder;
this.taskMessages = taskMessages;
this.system2 = system2;
} }


@Override @Override
try (DbSession dbSession = dbClient.openSession(false)) { try (DbSession dbSession = dbClient.openSession(false)) {
validateTargetBranch(dbSession); validateTargetBranch(dbSession);
Component root = treeRootHolder.getRoot(); Component root = treeRootHolder.getRoot();
// FIXME if module have really be dropped, no more need to load them
List<ComponentDto> baseModules = dbClient.componentDao().selectEnabledModulesFromProjectKey(dbSession, root.getDbKey()); List<ComponentDto> baseModules = dbClient.componentDao().selectEnabledModulesFromProjectKey(dbSession, root.getDbKey());
Map<String, ComponentDto> baseModulesByKey = baseModules.stream().collect(Collectors.toMap(ComponentDto::getDbKey, x -> x)); Map<String, ComponentDto> baseModulesByKey = baseModules.stream().collect(Collectors.toMap(ComponentDto::getDbKey, x -> x));
ValidateProjectsVisitor visitor = new ValidateProjectsVisitor(dbSession, dbClient.componentDao(), baseModulesByKey); ValidateProjectsVisitor visitor = new ValidateProjectsVisitor(dbSession, dbClient.componentDao(), baseModulesByKey);
@Override @Override
public void visitProject(Component rawProject) { public void visitProject(Component rawProject) {
String rawProjectKey = rawProject.getDbKey(); String rawProjectKey = rawProject.getDbKey();
Optional<ComponentDto> baseProject = loadBaseComponent(rawProjectKey);
validateAnalysisDate(baseProject);
Optional<ComponentDto> baseProjectOpt = loadBaseComponent(rawProjectKey);
validateAnalysisDate(baseProjectOpt);
if (!baseProjectOpt.isPresent()) {
return;
}
if (!isValidProjectKey(baseProjectOpt.get().getKey())) {
ComponentDto baseProject = baseProjectOpt.get();
// As it was possible in the past to use project key with a format that is no more compatible, we need to display a warning to the user in
// order for him to update his project key.
// SONAR-13191 This warning should be removed in 9.0, and instead the analysis should fail
taskMessages.add(new CeTaskMessages.Message(
format("The project key ‘%s’ contains invalid characters. %s. You should update the project key with the expected format.", baseProject.getKey(),
ALLOWED_CHARACTERS_MESSAGE),
system2.now()));
}
} }


private void validateAnalysisDate(Optional<ComponentDto> baseProject) { private void validateAnalysisDate(Optional<ComponentDto> baseProject) {
Long lastAnalysisDate = snapshotDto.map(SnapshotDto::getCreatedAt).orElse(null); Long lastAnalysisDate = snapshotDto.map(SnapshotDto::getCreatedAt).orElse(null);
if (lastAnalysisDate != null && currentAnalysisDate <= lastAnalysisDate) { if (lastAnalysisDate != null && currentAnalysisDate <= lastAnalysisDate) {
validationMessages.add(format("Date of analysis cannot be older than the date of the last known analysis on this project. Value: \"%s\". " + validationMessages.add(format("Date of analysis cannot be older than the date of the last known analysis on this project. Value: \"%s\". " +
"Latest analysis: \"%s\". It's only possible to rebuild the past in a chronological order.",
"Latest analysis: \"%s\". It's only possible to rebuild the past in a chronological order.",
formatDateTime(new Date(currentAnalysisDate)), formatDateTime(new Date(lastAnalysisDate)))); formatDateTime(new Date(currentAnalysisDate)), formatDateTime(new Date(lastAnalysisDate))));
} }
} }

+ 66
- 40
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ValidateProjectStepTest.java View File

import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.sonar.api.impl.utils.TestSystem2;
import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.MessageException; import org.sonar.api.utils.MessageException;
import org.sonar.api.utils.System2; import org.sonar.api.utils.System2;
import org.sonar.ce.task.log.CeTaskMessages;
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
import org.sonar.ce.task.projectanalysis.analysis.Branch; import org.sonar.ce.task.projectanalysis.analysis.Branch;
import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.component.Component;
import org.sonar.db.component.SnapshotTesting; import org.sonar.db.component.SnapshotTesting;


import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;


public class ValidateProjectStepTest { public class ValidateProjectStepTest {


static long DEFAULT_ANALYSIS_TIME = 1433131200000L; // 2015-06-01
static long PAST_ANALYSIS_TIME = 1_420_088_400_000L; // 2015-01-01
static long DEFAULT_ANALYSIS_TIME = 1_433_131_200_000L; // 2015-06-01
static long NOW = 1_500_000_000_000L;

static final String PROJECT_KEY = "PROJECT_KEY"; static final String PROJECT_KEY = "PROJECT_KEY";
static final Branch DEFAULT_BRANCH = new DefaultBranchImpl(); static final Branch DEFAULT_BRANCH = new DefaultBranchImpl();


@Rule @Rule
public DbTester dbTester = DbTester.create(System2.INSTANCE);

public DbTester db = DbTester.create(System2.INSTANCE);
@Rule @Rule
public ExpectedException thrown = ExpectedException.none(); public ExpectedException thrown = ExpectedException.none();

@Rule @Rule
public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();

@Rule @Rule
public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule() public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule()
.setAnalysisDate(new Date(DEFAULT_ANALYSIS_TIME)) .setAnalysisDate(new Date(DEFAULT_ANALYSIS_TIME))
.setBranch(DEFAULT_BRANCH); .setBranch(DEFAULT_BRANCH);
private System2 system2 = new TestSystem2().setNow(NOW);

private CeTaskMessages taskMessages = mock(CeTaskMessages.class);
private DbClient dbClient = db.getDbClient();


private DbClient dbClient = dbTester.getDbClient();
private ValidateProjectStep underTest = new ValidateProjectStep(dbClient, treeRootHolder, analysisMetadataHolder);
private ValidateProjectStep underTest = new ValidateProjectStep(dbClient, treeRootHolder, analysisMetadataHolder, taskMessages, system2);


@Test @Test
public void fail_if_pr_is_targeting_branch_with_modules() {
ComponentDto masterProject = dbTester.components().insertPublicProject();
ComponentDto mergeBranch = dbTester.components().insertProjectBranch(masterProject, b -> b.setKey("mergeBranch"));
dbClient.componentDao().insert(dbTester.getSession(), ComponentTesting.newModuleDto(mergeBranch));
setBranch(BranchType.PULL_REQUEST, mergeBranch.uuid());
dbTester.getSession().commit();
public void dont_fail_for_long_forked_from_master_with_modules() {
ComponentDto masterProject = db.components().insertPublicProject();
dbClient.componentDao().insert(db.getSession(), ComponentTesting.newModuleDto(masterProject));
setBranch(BranchType.BRANCH, masterProject.uuid());
db.getSession().commit();


treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("DEFG") treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("DEFG")
.setKey("branch") .setKey("branch")
.build()); .build());


thrown.expect(MessageException.class);
thrown.expectMessage("Due to an upgrade, you need first to re-analyze the target branch 'mergeBranch' before analyzing this pull request.");
underTest.execute(new TestComputationStepContext()); underTest.execute(new TestComputationStepContext());
verifyNoInteractions(taskMessages);
} }


@Test @Test
public void dont_fail_for_long_forked_from_master_with_modules() {
ComponentDto masterProject = dbTester.components().insertPublicProject();
dbClient.componentDao().insert(dbTester.getSession(), ComponentTesting.newModuleDto(masterProject));
setBranch(BranchType.BRANCH, masterProject.uuid());
dbTester.getSession().commit();
public void not_fail_if_analysis_date_is_after_last_analysis() {
ComponentDto project = ComponentTesting.newPrivateProjectDto(db.organizations().insert(), "ABCD").setDbKey(PROJECT_KEY);
dbClient.componentDao().insert(db.getSession(), project);
dbClient.snapshotDao().insert(db.getSession(), SnapshotTesting.newAnalysis(project).setCreatedAt(PAST_ANALYSIS_TIME));
db.getSession().commit();


treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("DEFG")
.setKey("branch")
.build());
treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").setKey(PROJECT_KEY).build());


underTest.execute(new TestComputationStepContext()); underTest.execute(new TestComputationStepContext());
} }


private void setBranch(BranchType type, @Nullable String mergeBranchUuid) {
Branch branch = mock(Branch.class);
when(branch.getType()).thenReturn(type);
when(branch.getReferenceBranchUuid()).thenReturn(mergeBranchUuid);
analysisMetadataHolder.setBranch(branch);
}

@Test @Test
public void not_fail_if_analysis_date_is_after_last_analysis() {
ComponentDto project = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), "ABCD").setDbKey(PROJECT_KEY);
dbClient.componentDao().insert(dbTester.getSession(), project);
dbClient.snapshotDao().insert(dbTester.getSession(), SnapshotTesting.newAnalysis(project).setCreatedAt(1420088400000L)); // 2015-01-01
dbTester.getSession().commit();
public void fail_if_pr_is_targeting_branch_with_modules() {
ComponentDto masterProject = db.components().insertPublicProject();
ComponentDto mergeBranch = db.components().insertProjectBranch(masterProject, b -> b.setKey("mergeBranch"));
dbClient.componentDao().insert(db.getSession(), ComponentTesting.newModuleDto(mergeBranch));
setBranch(BranchType.PULL_REQUEST, mergeBranch.uuid());
db.getSession().commit();


treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").setKey(PROJECT_KEY).build());
treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("DEFG")
.setKey("branch")
.build());


thrown.expect(MessageException.class);
thrown.expectMessage("Due to an upgrade, you need first to re-analyze the target branch 'mergeBranch' before analyzing this pull request.");
underTest.execute(new TestComputationStepContext()); underTest.execute(new TestComputationStepContext());
} }


public void fail_if_analysis_date_is_before_last_analysis() { public void fail_if_analysis_date_is_before_last_analysis() {
analysisMetadataHolder.setAnalysisDate(DateUtils.parseDate("2015-01-01")); analysisMetadataHolder.setAnalysisDate(DateUtils.parseDate("2015-01-01"));


ComponentDto project = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), "ABCD").setDbKey(PROJECT_KEY);
dbClient.componentDao().insert(dbTester.getSession(), project);
dbClient.snapshotDao().insert(dbTester.getSession(), SnapshotTesting.newAnalysis(project).setCreatedAt(1433131200000L)); // 2015-06-01
dbTester.getSession().commit();
ComponentDto project = ComponentTesting.newPrivateProjectDto(db.organizations().insert(), "ABCD").setDbKey(PROJECT_KEY);
dbClient.componentDao().insert(db.getSession(), project);
dbClient.snapshotDao().insert(db.getSession(), SnapshotTesting.newAnalysis(project).setCreatedAt(1433131200000L)); // 2015-06-01
db.getSession().commit();


treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").setKey(PROJECT_KEY).build()); treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").setKey(PROJECT_KEY).build());


underTest.execute(new TestComputationStepContext()); underTest.execute(new TestComputationStepContext());
} }


@Test
public void add_warning_when_project_key_is_invalid() {
ComponentDto project = db.components().insertPrivateProject(p -> p.setDbKey("inv$lid!"));
db.components().insertSnapshot(project, a -> a.setCreatedAt(PAST_ANALYSIS_TIME));
treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1)
.setUuid(project.uuid())
.setKey(project.getKey())
.build());

underTest.execute(new TestComputationStepContext());

verify(taskMessages, times(1))
.add(new CeTaskMessages.Message(
"The project key ‘inv$lid!’ contains invalid characters. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit. " +
"You should update the project key with the expected format.",
NOW));
}

private void setBranch(BranchType type, @Nullable String mergeBranchUuid) {
Branch branch = mock(Branch.class);
when(branch.getType()).thenReturn(type);
when(branch.getReferenceBranchUuid()).thenReturn(mergeBranchUuid);
analysisMetadataHolder.setBranch(branch);
}
} }

+ 1
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java View File

* @since 3.2 * @since 3.2
*/ */
public class ComponentKeyUpdaterDao implements Dao { public class ComponentKeyUpdaterDao implements Dao {

public void updateKey(DbSession dbSession, String projectUuid, String newKey) { public void updateKey(DbSession dbSession, String projectUuid, String newKey) {
ComponentKeyUpdaterMapper mapper = dbSession.getMapper(ComponentKeyUpdaterMapper.class); ComponentKeyUpdaterMapper mapper = dbSession.getMapper(ComponentKeyUpdaterMapper.class);
if (mapper.countResourceByKey(newKey) > 0) { if (mapper.countResourceByKey(newKey) > 0) {

+ 3
- 4
server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java View File

package org.sonar.db.component; package org.sonar.db.component;


import com.google.common.base.Strings; import com.google.common.base.Strings;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
ComponentDto project = db.components().insertPrivateProject(); ComponentDto project = db.components().insertPrivateProject();


thrown.expect(IllegalArgumentException.class); thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Malformed key for ' '. Project key cannot be empty nor contain whitespaces.");
thrown.expectMessage("Malformed key for 'my?project?key'. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.");


underTest.bulkUpdateKey(dbSession, project.uuid(), project.getDbKey(), " ", doNotReturnAnyRekeyedResource());
underTest.bulkUpdateKey(dbSession, project.uuid(), project.getDbKey(), "my?project?key", doNotReturnAnyRekeyedResource());
} }


@Test @Test


thrown.expect(IllegalArgumentException.class); thrown.expect(IllegalArgumentException.class);


underTest.simulateBulkUpdateKey(dbSession, "A", "project", " ");
underTest.simulateBulkUpdateKey(dbSession, "A", "project", "project?");
} }


@Test @Test

+ 2
- 7
server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentService.java View File

import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.singleton; import static java.util.Collections.singleton;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.sonar.core.component.ComponentKeys.isValidProjectKey;
import static org.sonar.server.exceptions.BadRequestException.checkRequest;
import static org.sonar.core.component.ComponentKeys.checkProjectKey;


@ServerSide @ServerSide
public class ComponentService { public class ComponentService {


public void updateKey(DbSession dbSession, ProjectDto project, String newKey) { public void updateKey(DbSession dbSession, ProjectDto project, String newKey) {
userSession.checkProjectPermission(UserRole.ADMIN, project); userSession.checkProjectPermission(UserRole.ADMIN, project);
checkProjectKeyFormat(newKey);
checkProjectKey(newKey);
dbClient.componentKeyUpdaterDao().updateKey(dbSession, project.getUuid(), newKey); dbClient.componentKeyUpdaterDao().updateKey(dbSession, project.getUuid(), newKey);
projectIndexers.commitAndIndexProjects(dbSession, singletonList(project), ProjectIndexer.Cause.PROJECT_KEY_UPDATE); projectIndexers.commitAndIndexProjects(dbSession, singletonList(project), ProjectIndexer.Cause.PROJECT_KEY_UPDATE);
Project newProject = new Project(project.getUuid(), newKey, project.getName(), project.getDescription(), project.getTags()); Project newProject = new Project(project.getUuid(), newKey, project.getName(), project.getDescription(), project.getTags());
return new RekeyedProject(project, rekeyedResource.getOldKey()); return new RekeyedProject(project, rekeyedResource.getOldKey());
} }


private static void checkProjectKeyFormat(String key) {
checkRequest(isValidProjectKey(key), "Malformed key for '%s'. It cannot be empty nor contain whitespaces.", key);
}

} }

+ 2
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java View File

import org.sonar.server.permission.PermissionTemplateService; import org.sonar.server.permission.PermissionTemplateService;


import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.sonar.api.resources.Qualifiers.APP;
import static org.sonar.api.resources.Qualifiers.PROJECT; import static org.sonar.api.resources.Qualifiers.PROJECT;
import static org.sonar.core.component.ComponentKeys.ALLOWED_CHARACTERS_MESSAGE;
import static org.sonar.core.component.ComponentKeys.isValidProjectKey; import static org.sonar.core.component.ComponentKeys.isValidProjectKey;
import static org.sonar.server.exceptions.BadRequestException.checkRequest; import static org.sonar.server.exceptions.BadRequestException.checkRequest;


} }


private void checkKeyFormat(String qualifier, String key) { private void checkKeyFormat(String qualifier, String key) {
checkRequest(isValidProjectKey(key), "Malformed key for %s: '%s'. It cannot be empty nor contain whitespaces.", getQualifierToDisplay(qualifier), key);
checkRequest(isValidProjectKey(key), "Malformed key for %s: '%s'. %s.", getQualifierToDisplay(qualifier), key, ALLOWED_CHARACTERS_MESSAGE);
} }


private String getQualifierToDisplay(String qualifier) { private String getQualifierToDisplay(String qualifier) {

+ 6
- 7
server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ComponentServiceUpdateKeyTest.java View File

import org.sonar.db.component.ComponentTesting; import org.sonar.db.component.ComponentTesting;
import org.sonar.server.es.ProjectIndexer; import org.sonar.server.es.ProjectIndexer;
import org.sonar.server.es.TestProjectIndexers; import org.sonar.server.es.TestProjectIndexers;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.project.Project; import org.sonar.server.project.Project;
import org.sonar.server.project.ProjectLifeCycleListeners; import org.sonar.server.project.ProjectLifeCycleListeners;
ComponentDto project = insertSampleProject(); ComponentDto project = insertSampleProject();
logInAsProjectAdministrator(project); logInAsProjectAdministrator(project);


expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Malformed key for ''. It cannot be empty nor contain whitespaces.");
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Malformed key for ''. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.");


underTest.updateKey(dbSession, componentDb.getProjectDto(project), ""); underTest.updateKey(dbSession, componentDb.getProjectDto(project), "");
} }


@Test @Test
public void fail_if_new_key_is_invalid() {
public void fail_if_new_key_is_not_formatted_correctly() {
ComponentDto project = insertSampleProject(); ComponentDto project = insertSampleProject();
logInAsProjectAdministrator(project); logInAsProjectAdministrator(project);


expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Malformed key for 'sample root'. It cannot be empty nor contain whitespaces.");
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Malformed key for 'sample?root'. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.");


underTest.updateKey(dbSession, componentDb.getProjectDto(project), "sample root");
underTest.updateKey(dbSession, componentDb.getProjectDto(project), "sample?root");
} }


@Test @Test

+ 5
- 5
server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java View File

@Test @Test
public void fail_when_key_has_bad_format() { public void fail_when_key_has_bad_format() {
expectedException.expect(BadRequestException.class); expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Malformed key for Project: ' '");
expectedException.expectMessage("Malformed key for Project: '1234'");


underTest.create(db.getSession(), underTest.create(db.getSession(),
NewComponent.newComponentBuilder() NewComponent.newComponentBuilder()
.setKey(" ")
.setKey("1234")
.setName(DEFAULT_PROJECT_NAME) .setName(DEFAULT_PROJECT_NAME)
.setOrganizationUuid(db.getDefaultOrganization().getUuid()) .setOrganizationUuid(db.getDefaultOrganization().getUuid())
.build(), .build(),
} }


@Test @Test
public void properly_fail_when_key_contains_percent_character() {
public void fail_when_key_contains_percent_character() {
expectedException.expect(BadRequestException.class); expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Malformed key for Project: ' '");
expectedException.expectMessage("Malformed key for Project: 'roject%Key'");


underTest.create(db.getSession(), underTest.create(db.getSession(),
NewComponent.newComponentBuilder() NewComponent.newComponentBuilder()
.setKey(" ")
.setKey("roject%Key")
.setName(DEFAULT_PROJECT_NAME) .setName(DEFAULT_PROJECT_NAME)
.setOrganizationUuid(db.getDefaultOrganization().getUuid()) .setOrganizationUuid(db.getDefaultOrganization().getUuid())
.build(), .build(),

+ 5
- 4
server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/BulkUpdateKeyActionTest.java View File

import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_TO; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_TO;


public class BulkUpdateKeyActionTest { public class BulkUpdateKeyActionTest {

private static final String MY_PROJECT_KEY = "my_project"; private static final String MY_PROJECT_KEY = "my_project";
private static final String FROM = "my_"; private static final String FROM = "my_";
private static final String TO = "your_"; private static final String TO = "your_";
insertMyProject(); insertMyProject();


expectedException.expect(IllegalArgumentException.class); expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Malformed key for 'my aproject'. Project key cannot be empty nor contain whitespaces.");
expectedException.expectMessage("Malformed key for 'my?project'. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.");


callByKey(MY_PROJECT_KEY, FROM, "my a");
callByKey(MY_PROJECT_KEY, FROM, "my?");
} }


@Test @Test
insertMyProject(); insertMyProject();


expectedException.expect(IllegalArgumentException.class); expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Malformed key for 'my aproject'. Project key cannot be empty nor contain whitespaces.");
expectedException.expectMessage("Malformed key for 'my?project'. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.");


callDryRunByKey(MY_PROJECT_KEY, FROM, "my a");
callDryRunByKey(MY_PROJECT_KEY, FROM, "my?");
} }


@Test @Test

+ 3
- 3
server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/CreateActionTest.java View File

} }


@Test @Test
public void properly_fail_when_invalid_project_key() {
public void fail_when_invalid_project_key() {
userSession.addPermission(PROVISION_PROJECTS, db.getDefaultOrganization()); userSession.addPermission(PROVISION_PROJECTS, db.getDefaultOrganization());


expectedException.expect(BadRequestException.class); expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Malformed key for Project: 'project Key'. It cannot be empty nor contain whitespaces.");
expectedException.expectMessage("Malformed key for Project: 'project%Key'. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.");


call(CreateRequest.builder() call(CreateRequest.builder()
.setProjectKey("project Key")
.setProjectKey("project%Key")
.setName(DEFAULT_PROJECT_NAME) .setName(DEFAULT_PROJECT_NAME)
.build()); .build());
} }

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

*/ */
package org.sonar.core.component; package org.sonar.core.component;


import java.util.regex.Pattern;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;




public static final int MAX_COMPONENT_KEY_LENGTH = 400; public static final int MAX_COMPONENT_KEY_LENGTH = 400;


/*
* Must not be blank or empty
*/
private static final String VALID_PROJECT_KEY_REGEXP = "[^\\p{javaWhitespace}]+";
public static final String ALLOWED_CHARACTERS_MESSAGE = "Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit";

public static final String MALFORMED_KEY_MESSAGE = "Malformed key for '%s'. %s.";


/*
* Allowed characters are alphanumeric, '-', '_', '.' and '/'
/**
* Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit
*/ */
private static final String VALID_BRANCH_REGEXP = "[\\p{Alnum}\\-_./]*";
private static final Pattern VALID_PROJECT_KEY_REGEXP = Pattern.compile("[\\p{Alnum}\\-_.:]*[\\p{Alpha}\\-_.:]+[\\p{Alnum}\\-_.:]*");


private static final String KEY_WITH_BRANCH_FORMAT = "%s:%s"; private static final String KEY_WITH_BRANCH_FORMAT = "%s:%s";


return sb.toString(); return sb.toString();
} }


/**
* Test if given parameter is valid for a project. A key is valid if it doesn't contain whitespaces.
*
* @return <code>true</code> if <code>keyCandidate</code> can be used for a project
*/
public static boolean isValidProjectKey(String keyCandidate) { public static boolean isValidProjectKey(String keyCandidate) {
return keyCandidate.matches(VALID_PROJECT_KEY_REGEXP);
return VALID_PROJECT_KEY_REGEXP.matcher(keyCandidate).matches();
} }


/** /**
* @throws IllegalArgumentException if the format is incorrect * @throws IllegalArgumentException if the format is incorrect
*/ */
public static void checkProjectKey(String keyCandidate) { public static void checkProjectKey(String keyCandidate) {
checkArgument(isValidProjectKey(keyCandidate), "Malformed key for '%s'. %s", keyCandidate, "Project key cannot be empty nor contain whitespaces.");
checkArgument(isValidProjectKey(keyCandidate), MALFORMED_KEY_MESSAGE, keyCandidate, ALLOWED_CHARACTERS_MESSAGE);
} }


/** /**

+ 25
- 4
sonar-core/src/test/java/org/sonar/core/component/ComponentKeysTest.java View File

} }


@Test @Test
public void isValidProjectKey() {
public void valid_project_key() {
assertThat(ComponentKeys.isValidProjectKey("abc")).isTrue(); assertThat(ComponentKeys.isValidProjectKey("abc")).isTrue();
assertThat(ComponentKeys.isValidProjectKey("0123")).isTrue();
assertThat(ComponentKeys.isValidProjectKey("ab_12")).isTrue(); assertThat(ComponentKeys.isValidProjectKey("ab_12")).isTrue();
assertThat(ComponentKeys.isValidProjectKey("ab/12")).isTrue();
assertThat(ComponentKeys.isValidProjectKey("코드품질")).isTrue();
}

@Test
public void invalid_project_key() {
assertThat(ComponentKeys.isValidProjectKey("0123")).isFalse();

assertThat(ComponentKeys.isValidProjectKey("ab/12")).isFalse();
assertThat(ComponentKeys.isValidProjectKey("코드품질")).isFalse();

assertThat(ComponentKeys.isValidProjectKey("")).isFalse(); assertThat(ComponentKeys.isValidProjectKey("")).isFalse();
assertThat(ComponentKeys.isValidProjectKey(" ")).isFalse(); assertThat(ComponentKeys.isValidProjectKey(" ")).isFalse();
assertThat(ComponentKeys.isValidProjectKey("ab 12")).isFalse(); assertThat(ComponentKeys.isValidProjectKey("ab 12")).isFalse();


ComponentKeys.checkProjectKey("ab 12"); ComponentKeys.checkProjectKey("ab 12");
} }

@Test
public void checkProjectKey_fail_if_only_digit() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Malformed key for '0123'. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.");

ComponentKeys.checkProjectKey("0123");
}

@Test
public void checkProjectKey_fail_if_special_characters_not_allowed() {
expectedException.expect(IllegalArgumentException.class);

ComponentKeys.checkProjectKey("ab/12");
}
} }

+ 14
- 5
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectReactorValidator.java View File

package org.sonar.scanner.scan; package org.sonar.scanner.scan;


import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.api.batch.bootstrap.ProjectReactor;
import org.sonar.api.utils.MessageException; import org.sonar.api.utils.MessageException;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.core.component.ComponentKeys; import org.sonar.core.component.ComponentKeys;
import org.sonar.scanner.bootstrap.GlobalConfiguration; import org.sonar.scanner.bootstrap.GlobalConfiguration;
import org.sonar.scanner.scan.branch.BranchParamsValidator; import org.sonar.scanner.scan.branch.BranchParamsValidator;


import static java.lang.String.format; import static java.lang.String.format;
import static java.util.Collections.singletonList;
import static java.util.Objects.nonNull; import static java.util.Objects.nonNull;
import static org.apache.commons.lang.StringUtils.isNotEmpty; import static org.apache.commons.lang.StringUtils.isNotEmpty;
import static org.sonar.core.component.ComponentKeys.ALLOWED_CHARACTERS_MESSAGE;
import static org.sonar.core.config.ScannerProperties.BRANCHES_DOC_LINK; import static org.sonar.core.config.ScannerProperties.BRANCHES_DOC_LINK;
import static org.sonar.core.config.ScannerProperties.BRANCH_NAME; import static org.sonar.core.config.ScannerProperties.BRANCH_NAME;
import static org.sonar.core.config.ScannerProperties.PULL_REQUEST_BASE; import static org.sonar.core.config.ScannerProperties.PULL_REQUEST_BASE;
* @since 3.6 * @since 3.6
*/ */
public class ProjectReactorValidator { public class ProjectReactorValidator {

private static final Logger LOG = Loggers.get(ProjectReactorValidator.class);

private final GlobalConfiguration settings; private final GlobalConfiguration settings;


// null = branch plugin is not available // null = branch plugin is not available
List<String> validationMessages = new ArrayList<>(); List<String> validationMessages = new ArrayList<>();


for (ProjectDefinition moduleDef : reactor.getProjects()) { for (ProjectDefinition moduleDef : reactor.getProjects()) {
validateModule(moduleDef, validationMessages);
validateModule(moduleDef);
} }


if (isBranchFeatureAvailable()) { if (isBranchFeatureAvailable()) {
} }


private void validateBranchParamsWhenPluginAbsent(List<String> validationMessages) { private void validateBranchParamsWhenPluginAbsent(List<String> validationMessages) {
for (String param : Arrays.asList(BRANCH_NAME)) {
for (String param : singletonList(BRANCH_NAME)) {
if (isNotEmpty(settings.get(param).orElse(null))) { if (isNotEmpty(settings.get(param).orElse(null))) {
validationMessages.add(format("To use the property \"%s\" and analyze branches, Developer Edition or above is required. " validationMessages.add(format("To use the property \"%s\" and analyze branches, Developer Edition or above is required. "
+ "See %s for more information.", param, BRANCHES_DOC_LINK)); + "See %s for more information.", param, BRANCHES_DOC_LINK));
+ "See %s for more information.", param, BRANCHES_DOC_LINK))); + "See %s for more information.", param, BRANCHES_DOC_LINK)));
} }


private static void validateModule(ProjectDefinition moduleDef, List<String> validationMessages) {
private static void validateModule(ProjectDefinition moduleDef) {
if (!ComponentKeys.isValidProjectKey(moduleDef.getKey())) { if (!ComponentKeys.isValidProjectKey(moduleDef.getKey())) {
validationMessages.add(format("\"%s\" is not a valid project or module key. It cannot be empty nor contain whitespaces.", moduleDef.getKey()));
// As it was possible in the past to use project key with a format that is no more compatible, we need to display a warning to the user in
// order for him to update his project key.
// SONAR-13191 This warning should be removed in 9.0
LOG.warn("\"{}\" is not a valid project or module key. {}.", moduleDef.getKey(), ALLOWED_CHARACTERS_MESSAGE);
} }
} }



+ 35
- 4
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectReactorValidatorTest.java View File

import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.api.batch.bootstrap.ProjectReactor;
import org.sonar.api.utils.MessageException; import org.sonar.api.utils.MessageException;
import org.sonar.api.utils.log.LogAndArguments;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.core.config.ScannerProperties; import org.sonar.core.config.ScannerProperties;
import org.sonar.scanner.ProjectInfo; import org.sonar.scanner.ProjectInfo;
import org.sonar.scanner.bootstrap.GlobalConfiguration; import org.sonar.scanner.bootstrap.GlobalConfiguration;


import static org.apache.commons.lang.RandomStringUtils.randomAscii; import static org.apache.commons.lang.RandomStringUtils.randomAscii;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@Rule @Rule
public ExpectedException thrown = ExpectedException.none(); public ExpectedException thrown = ExpectedException.none();


@Rule
public LogTester logTester = new LogTester();

private GlobalConfiguration settings = mock(GlobalConfiguration.class); private GlobalConfiguration settings = mock(GlobalConfiguration.class);
private ProjectInfo projectInfo = mock(ProjectInfo.class); private ProjectInfo projectInfo = mock(ProjectInfo.class);
private ProjectReactorValidator underTest = new ProjectReactorValidator(settings); private ProjectReactorValidator underTest = new ProjectReactorValidator(settings);
} }


@Test @Test
public void fail_with_invalid_key() {
ProjectReactor reactor = createProjectReactor(" ");
public void log_warning_when_invalid_key() {
ProjectReactor reactor = createProjectReactor("foo$bar");

underTest.validate(reactor);

assertThat(logTester.getLogs(LoggerLevel.WARN))
.extracting(LogAndArguments::getFormattedMsg)
.containsOnly("\"foo$bar\" is not a valid project or module key. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.");
}

@Test
public void log_warning_when_only_digits() {
ProjectReactor reactor = createProjectReactor("12345");

underTest.validate(reactor);

assertThat(logTester.getLogs(LoggerLevel.WARN))
.extracting(LogAndArguments::getFormattedMsg)
.containsOnly("\"12345\" is not a valid project or module key. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.");
}

@Test
public void log_warning_when_backslash_in_key() {
ProjectReactor reactor = createProjectReactor("foo\\bar");


thrown.expect(MessageException.class);
thrown.expectMessage("\" \" is not a valid project or module key");
underTest.validate(reactor); underTest.validate(reactor);

assertThat(logTester.getLogs(LoggerLevel.WARN))
.extracting(LogAndArguments::getFormattedMsg)
.containsOnly("\"foo\\bar\" is not a valid project or module key. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.");
} }


@Test @Test

Loading…
Cancel
Save