diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2016-10-25 14:19:25 +0200 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@sonarsource.com> | 2016-10-25 15:34:48 +0200 |
commit | 8836dd4591462784a67c9adc77764ca2b876f241 (patch) | |
tree | 065a0d9ee8e5a4b237b3382742548afbf09bc04e | |
parent | 6dce6c46c7ebfa4408b09d8be40b519671afb1c5 (diff) | |
download | sonarqube-8836dd4591462784a67c9adc77764ca2b876f241.tar.gz sonarqube-8836dd4591462784a67c9adc77764ca2b876f241.zip |
SONAR-8160 WS measures/search handles permission check on several projects
3 files changed, 93 insertions, 59 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchAction.java index f29df831428..99fb6524764 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchAction.java @@ -32,11 +32,12 @@ import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collector; -import java.util.stream.Collectors; import java.util.stream.Stream; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; +import org.sonar.api.web.UserRole; +import org.sonar.core.util.stream.Collectors; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.component.ComponentDto; @@ -44,16 +45,20 @@ import org.sonar.db.component.SnapshotDto; import org.sonar.db.measure.MeasureDto; import org.sonar.db.measure.MeasureQuery; import org.sonar.db.metric.MetricDto; +import org.sonar.server.user.UserSession; import org.sonarqube.ws.WsMeasures; import org.sonarqube.ws.WsMeasures.Measure; import org.sonarqube.ws.WsMeasures.SearchWsResponse; import org.sonarqube.ws.WsMeasures.SearchWsResponse.Component; import org.sonarqube.ws.client.measure.SearchRequest; -import static com.google.common.base.Preconditions.checkArgument; import static java.util.Comparator.comparing; import static java.util.Objects.requireNonNull; import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; +import static org.sonar.core.util.stream.Collectors.toList; +import static org.sonar.core.util.stream.Collectors.toSet; +import static org.sonar.core.util.stream.Collectors.uniqueIndex; import static org.sonar.server.measure.ws.ComponentDtoToWsComponent.dbToWsComponent; import static org.sonar.server.measure.ws.MeasureDtoToWsMeasure.dbToWsMeasure; import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createMetricKeysParameter; @@ -71,9 +76,11 @@ import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRIC_ public class SearchAction implements MeasuresWsAction { + private final UserSession userSession; private final DbClient dbClient; - public SearchAction(DbClient dbClient) { + public SearchAction(UserSession userSession, DbClient dbClient) { + this.userSession = userSession; this.dbClient = dbClient; } @@ -138,7 +145,7 @@ public class SearchAction implements MeasuresWsAction { private List<SnapshotDto> searchSnapshots() { requireNonNull(components); - Set<String> projectUuids = components.stream().map(ComponentDto::projectUuid).collect(Collectors.toSet()); + Set<String> projectUuids = components.stream().map(ComponentDto::projectUuid).collect(toSet()); return dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, projectUuids); } @@ -154,7 +161,7 @@ public class SearchAction implements MeasuresWsAction { private List<MetricDto> searchMetrics() { requireNonNull(request); List<MetricDto> dbMetrics = dbClient.metricDao().selectByKeys(dbSession, request.getMetricKeys()); - List<String> metricKeys = dbMetrics.stream().map(MetricDto::getKey).collect(Collectors.toList()); + List<String> metricKeys = dbMetrics.stream().map(MetricDto::getKey).collect(toList()); checkRequest(request.getMetricKeys().size() == dbMetrics.size(), "The following metrics are not found: %s", String.join(", ", difference(request.getMetricKeys(), metricKeys))); return dbMetrics; @@ -162,11 +169,19 @@ public class SearchAction implements MeasuresWsAction { private List<ComponentDto> searchComponents() { requireNonNull(request); - List<ComponentDto> componentsByKey = searchByComponentKeys(dbSession, request.getComponentKeys()); - List<String> componentKeys = componentsByKey.stream().map(ComponentDto::key).collect(Collectors.toList()); - checkArgument(componentsByKey.size() == request.getComponentKeys().size(), "The following component keys are not found: %s", - String.join(", ", difference(request.getComponentKeys(), componentKeys))); - return componentsByKey; + return getAuthorizedComponents(searchByComponentKeys(dbSession, request.getComponentKeys())); + } + + private List<ComponentDto> getAuthorizedComponents(List<ComponentDto> componentDtos) { + List<String> projectUuids = componentDtos.stream().map(ComponentDto::getRootUuid).collect(Collectors.toList()); + List<ComponentDto> projects = dbClient.componentDao().selectByUuids(dbSession, projectUuids); + Map<String, Long> projectIdsByUuids = projects.stream().collect(uniqueIndex(ComponentDto::uuid, ComponentDto::getId)); + Collection<Long> authorizedProjectIds = dbClient.authorizationDao().keepAuthorizedProjectIds(dbSession, + projects.stream().map(ComponentDto::getId).collect(toList()), + userSession.getUserId(), UserRole.USER); + return componentDtos.stream() + .filter(c -> authorizedProjectIds.contains(projectIdsByUuids.get(c.projectUuid()))) + .collect(Collectors.toList()); } private List<ComponentDto> searchByComponentKeys(DbSession dbSession, List<String> componentKeys) { @@ -178,8 +193,8 @@ public class SearchAction implements MeasuresWsAction { requireNonNull(metrics); return dbClient.measureDao().selectByQuery(dbSession, MeasureQuery.builder() - .setComponentUuids(components.stream().map(ComponentDto::uuid).collect(Collectors.toList())) - .setMetricIds(metrics.stream().map(MetricDto::getId).collect(Collectors.toList())) + .setComponentUuids(components.stream().map(ComponentDto::uuid).collect(toList())) + .setMetricIds(metrics.stream().map(MetricDto::getId).collect(toList())) .build()); } @@ -189,7 +204,7 @@ public class SearchAction implements MeasuresWsAction { return expected.stream() .filter(value -> !actualSet.contains(value)) .sorted(String::compareTo) - .collect(Collectors.toList()); + .collect(toList()); } private SearchWsResponse buildResponse() { @@ -211,12 +226,12 @@ public class SearchAction implements MeasuresWsAction { return components.stream() .map(dbToWsComponent()) .sorted(comparing(Component::getName)) - .collect(Collectors.toList()); + .collect(toList()); } private List<Measure> buildWsMeasures() { - Map<String, String> componentNamesByUuid = components.stream().collect(Collectors.toMap(ComponentDto::uuid, ComponentDto::name)); - Map<Integer, MetricDto> metricsById = metrics.stream().collect(Collectors.toMap(MetricDto::getId, identity())); + Map<String, String> componentNamesByUuid = components.stream().collect(toMap(ComponentDto::uuid, ComponentDto::name)); + Map<Integer, MetricDto> metricsById = metrics.stream().collect(toMap(MetricDto::getId, identity())); Function<MeasureDto, MetricDto> dbMeasureToDbMetric = dbMeasure -> metricsById.get(dbMeasure.getMetricId()); Function<Measure, String> byMetricKey = Measure::getMetric; @@ -226,13 +241,13 @@ public class SearchAction implements MeasuresWsAction { .concat(measures.stream(), buildBestMeasures().stream()) .map(dbMeasure -> dbToWsMeasure(dbMeasure, dbMeasureToDbMetric.apply(dbMeasure))) .sorted(comparing(byMetricKey).thenComparing(byComponentName)) - .collect(Collectors.toList()); + .collect(toList()); } private List<MeasureDto> buildBestMeasures() { Set<MetricDto> metricsWithBestValue = metrics.stream() .filter(metric -> metric.isOptimizedBestValue() && metric.getBestValue() != null) - .collect(Collectors.toSet()); + .collect(toSet()); Multimap<String, WsMeasures.Period> wsPeriodsByProjectUuid = snapshots.stream().collect(Collector.of( ImmutableMultimap::<String, WsMeasures.Period>builder, @@ -256,7 +271,7 @@ public class SearchAction implements MeasuresWsAction { .flatMap(component -> metricsWithBestValue.stream() .filter(doesNotHaveAMeasureInDb.apply(component)) .map(buildBestMeasure(component, wsPeriodsByProjectUuid.get(component.projectUuid())))) - .collect(Collectors.toList()); + .collect(toList()); } } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/SearchActionTest.java index 15607c6ce42..082db028f88 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/SearchActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/SearchActionTest.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; import javax.annotation.Nullable; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -35,6 +36,7 @@ import org.sonar.api.measures.Metric; import org.sonar.api.resources.Qualifiers; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; @@ -42,6 +44,7 @@ import org.sonar.db.component.ComponentDbTester; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.SnapshotDto; import org.sonar.db.metric.MetricDto; +import org.sonar.db.user.UserDto; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.TestRequest; @@ -49,8 +52,10 @@ import org.sonar.server.ws.WsActionTester; import org.sonarqube.ws.WsMeasures; import org.sonarqube.ws.WsMeasures.Measure; import org.sonarqube.ws.WsMeasures.SearchWsResponse; +import org.sonarqube.ws.WsMeasures.SearchWsResponse.Component; import static com.google.common.collect.Lists.newArrayList; +import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; @@ -82,7 +87,15 @@ public class SearchActionTest { DbClient dbClient = db.getDbClient(); DbSession dbSession = db.getSession(); - WsActionTester ws = new WsActionTester(new SearchAction(dbClient)); + UserDto user; + + WsActionTester ws = new WsActionTester(new SearchAction(userSession, dbClient)); + + @Before + public void setUp() throws Exception { + user = db.users().insertUser("john"); + userSession.login(user); + } @Test public void json_example() { @@ -101,11 +114,12 @@ public class SearchActionTest { public void project_without_measures_map_all_fields() { ComponentDto dbComponent = componentDb.insertComponent(newProjectDto()); insertComplexityMetric(); + setBrowsePermissionOnUser(dbComponent); SearchWsResponse result = call(singletonList(dbComponent.key()), singletonList("complexity")); assertThat(result.getComponentsCount()).isEqualTo(1); - SearchWsResponse.Component wsComponent = result.getComponents(0); + Component wsComponent = result.getComponents(0); assertThat(wsComponent.getKey()).isEqualTo(dbComponent.key()); assertThat(wsComponent.getName()).isEqualTo(dbComponent.name()); } @@ -113,6 +127,7 @@ public class SearchActionTest { @Test public void search_by_component_key() { ComponentDto project = componentDb.insertProject(); + setBrowsePermissionOnUser(project); insertComplexityMetric(); SearchWsResponse result = call(singletonList(project.key()), singletonList("complexity")); @@ -125,6 +140,7 @@ public class SearchActionTest { public void return_measures() throws Exception { ComponentDto project = newProjectDto(); SnapshotDto projectSnapshot = componentDb.insertProjectAndSnapshot(project); + setBrowsePermissionOnUser(project); MetricDto coverage = insertCoverageMetric(); dbClient.measureDao().insert(dbSession, newMeasureDto(coverage, project, projectSnapshot).setValue(15.5d)); db.commit(); @@ -142,6 +158,7 @@ public class SearchActionTest { public void return_measures_on_periods() throws Exception { ComponentDto project = newProjectDto(); SnapshotDto projectSnapshot = componentDb.insertProjectAndSnapshot(project); + setBrowsePermissionOnUser(project); MetricDto coverage = insertCoverageMetric(); dbClient.measureDao().insert(dbSession, newMeasureDto(coverage, project, projectSnapshot) @@ -186,6 +203,7 @@ public class SearchActionTest { newMeasureDto(coverage, file, projectSnapshot).setValue(15.5d), newMeasureDto(coverage, directoryDto, projectSnapshot).setValue(42.0d)); db.commit(); + setBrowsePermissionOnUser(projectDto); SearchWsResponse result = call(newArrayList(directoryDto.key(), file.key()), newArrayList("ncloc", "coverage", "new_violations")); @@ -201,8 +219,23 @@ public class SearchActionTest { } @Test + public void only_returns_authorized_components() { + insertComplexityMetric(); + ComponentDto project1 = componentDb.insertProject(); + ComponentDto file1 = componentDb.insertComponent(newFileDto(project1)); + setBrowsePermissionOnUser(project1); + ComponentDto project2 = componentDb.insertProject(); + ComponentDto file2 = componentDb.insertComponent(newFileDto(project2)); + + SearchWsResponse result = call(asList(file1.key(), file2.key()), singletonList("complexity")); + + assertThat(result.getComponentsList()).extracting(Component::getKey).containsOnly(file1.key()); + } + + @Test public void fail_if_no_metric() { ComponentDto project = componentDb.insertProject(); + setBrowsePermissionOnUser(project); expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("The 'metricKeys' parameter is missing"); @@ -213,6 +246,7 @@ public class SearchActionTest { @Test public void fail_if_empty_metric() { ComponentDto project = componentDb.insertProject(); + setBrowsePermissionOnUser(project); expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Metric keys must be provided"); @@ -223,6 +257,7 @@ public class SearchActionTest { @Test public void fail_if_unknown_metric() { ComponentDto project = componentDb.insertProject(); + setBrowsePermissionOnUser(project); insertComplexityMetric(); expectedException.expect(BadRequestException.class); @@ -252,31 +287,6 @@ public class SearchActionTest { } @Test - public void fail_if_unknown_component_key() { - insertComplexityMetric(); - ComponentDto project = componentDb.insertProject(); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("The following component keys are not found: ANOTHER_PROJECT_KEY, YOUR_PROJECT_KEY"); - - call(newArrayList("YOUR_PROJECT_KEY", project.key(), "ANOTHER_PROJECT_KEY"), singletonList("complexity")); - } - - @Test - public void fail_if_more_than_100_component_id() { - List<String> uuids = IntStream.rangeClosed(1, 101) - .mapToObj(i -> componentDb.insertProject()) - .map(ComponentDto::uuid) - .collect(Collectors.toList()); - insertComplexityMetric(); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("101 components provided, more than maximum authorized (100)"); - - call(uuids, singletonList("complexity")); - } - - @Test public void fail_if_more_than_100_component_key() { List<String> keys = IntStream.rangeClosed(1, 101) .mapToObj(i -> componentDb.insertProject()) @@ -476,8 +486,13 @@ public class SearchActionTest { .setVariation(1, 255.0d) .setVariation(2, 0.0d) .setVariation(3, 255.0d)); - db.commit(); + setBrowsePermissionOnUser(project); return componentKeys; } + + private void setBrowsePermissionOnUser(ComponentDto project) { + db.users().insertProjectPermissionOnUser(user, UserRole.USER, project); + dbSession.commit(); + } } diff --git a/sonar-db/src/test/java/org/sonar/db/component/ComponentTesting.java b/sonar-db/src/test/java/org/sonar/db/component/ComponentTesting.java index e1b2f364b3b..f26cb90d4b0 100644 --- a/sonar-db/src/test/java/org/sonar/db/component/ComponentTesting.java +++ b/sonar-db/src/test/java/org/sonar/db/component/ComponentTesting.java @@ -30,6 +30,10 @@ import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR; public class ComponentTesting { + public static ComponentDto newFileDto(ComponentDto subProjectOrProject) { + return newFileDto(subProjectOrProject, null); + } + public static ComponentDto newFileDto(ComponentDto subProjectOrProject, @Nullable ComponentDto directory) { return newFileDto(subProjectOrProject, directory, Uuids.create()); } @@ -53,21 +57,21 @@ public class ComponentTesting { public static ComponentDto newDirectory(ComponentDto module, String uuid, String path) { return newChildComponent(uuid, module, module) - .setKey(!path.equals("/") ? module.getKey() + ":" + path : module.getKey() + ":/") - .setName(path) - .setLongName(path) - .setPath(path) - .setScope(Scopes.DIRECTORY) - .setQualifier(Qualifiers.DIRECTORY); + .setKey(!path.equals("/") ? module.getKey() + ":" + path : module.getKey() + ":/") + .setName(path) + .setLongName(path) + .setPath(path) + .setScope(Scopes.DIRECTORY) + .setQualifier(Qualifiers.DIRECTORY); } public static ComponentDto newSubView(ComponentDto viewOrSubView, String uuid, String key) { return newChildComponent(uuid, viewOrSubView, viewOrSubView) - .setKey(key) - .setName(key) - .setLongName(key) - .setScope(Scopes.PROJECT) - .setQualifier(Qualifiers.SUBVIEW); + .setKey(key) + .setName(key) + .setLongName(key) + .setScope(Scopes.PROJECT) + .setQualifier(Qualifiers.SUBVIEW); } public static ComponentDto newModuleDto(String uuid, ComponentDto parentModuleOrProject) { @@ -120,7 +124,7 @@ public class ComponentTesting { .setName(name) .setLongName(name) .setScope(Scopes.PROJECT) - // XXX No constant ! + // XXX No constant ! .setQualifier("DEV") .setPath(null) .setLanguage(null) |