From 0cde042754aba04f6c46ed5a1c73d8989b3f5d52 Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 18 Dec 2019 08:48:30 +0100 Subject: [PATCH] SONAR-12834 filter hotspots by current user --- .../sonar/server/hotspot/ws/SearchAction.java | 37 ++++++-- .../server/hotspot/ws/SearchActionTest.java | 89 ++++++++++++++++++- 2 files changed, 120 insertions(+), 6 deletions(-) diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/SearchAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/SearchAction.java index 3d3c6ec477b..8609c927d79 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/SearchAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/SearchAction.java @@ -22,6 +22,7 @@ package org.sonar.server.hotspot.ws; import com.google.common.collect.ImmutableSet; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -90,6 +91,7 @@ public class SearchAction implements HotspotsWsAction { private static final String PARAM_BRANCH = "branch"; private static final String PARAM_PULL_REQUEST = "pullRequest"; private static final String PARAM_SINCE_LEAK_PERIOD = "sinceLeakPeriod"; + private static final String PARAM_ONLY_MINE = "onlyMine"; private final DbClient dbClient; private final UserSession userSession; @@ -146,6 +148,11 @@ public class SearchAction implements HotspotsWsAction { .setDescription("If '%s' is provided, only Security Hotspots created since the leak period are returned.") .setBooleanPossibleValues() .setDefaultValue("false"); + action.createParam(PARAM_ONLY_MINE) + .setDescription("If 'projectKey' is provided, returns only Security Hotspots assigned to the current user") + .setBooleanPossibleValues() + .setRequired(false); + // FIXME add response example and test it // action.setResponseExample() } @@ -172,15 +179,15 @@ public class SearchAction implements HotspotsWsAction { request.param(PARAM_PROJECT_KEY), request.param(PARAM_BRANCH), request.param(PARAM_PULL_REQUEST), hotspotKeys, request.param(PARAM_STATUS), request.param(PARAM_RESOLUTION), - request.paramAsBoolean(PARAM_SINCE_LEAK_PERIOD)); + request.paramAsBoolean(PARAM_SINCE_LEAK_PERIOD), + request.paramAsBoolean(PARAM_ONLY_MINE)); } - private static void validateParameters(WsRequest wsRequest) { + private void validateParameters(WsRequest wsRequest) { Optional projectKey = wsRequest.getProjectKey(); Optional branch = wsRequest.getBranch(); Optional pullRequest = wsRequest.getPullRequest(); Set hotspotKeys = wsRequest.getHotspotKeys(); - checkArgument( projectKey.isPresent() || !hotspotKeys.isEmpty(), "A value must be provided for either parameter '%s' or parameter '%s'", PARAM_PROJECT_KEY, PARAM_HOTSPOTS); @@ -206,6 +213,13 @@ public class SearchAction implements HotspotsWsAction { r -> checkArgument(status.filter(STATUS_REVIEWED::equals).isPresent(), "Value '%s' of parameter '%s' can only be provided if value of parameter '%s' is '%s'", r, PARAM_RESOLUTION, PARAM_STATUS, STATUS_REVIEWED)); + + if (wsRequest.isOnlyMine()) { + checkArgument(userSession.isLoggedIn(), + "Parameter '%s' requires user to be logged in", PARAM_ONLY_MINE); + checkArgument(wsRequest.getProjectKey().isPresent(), + "Parameter '%s' can be used with parameter '%s' only", PARAM_ONLY_MINE, PARAM_PROJECT_KEY); + } } private Optional getAndValidateProject(DbSession dbSession, WsRequest wsRequest) { @@ -276,6 +290,12 @@ public class SearchAction implements HotspotsWsAction { if (!hotspotKeys.isEmpty()) { builder.issueKeys(hotspotKeys); } + + if (wsRequest.isOnlyMine()) { + userSession.checkLoggedIn(); + builder.assigneeUuids(Collections.singletonList(userSession.getUuid())); + } + wsRequest.getStatus().ifPresent(status -> builder.resolved(STATUS_REVIEWED.equals(status))); wsRequest.getResolution().ifPresent(resolution -> builder.resolutions(singleton(resolution))); @@ -380,11 +400,13 @@ public class SearchAction implements HotspotsWsAction { private final String status; private final String resolution; private final boolean sinceLeakPeriod; + private final boolean onlyMine; private WsRequest(int page, int index, @Nullable String projectKey, @Nullable String branch, @Nullable String pullRequest, Set hotspotKeys, - @Nullable String status, @Nullable String resolution, @Nullable Boolean sinceLeakPeriod) { + @Nullable String status, @Nullable String resolution, @Nullable Boolean sinceLeakPeriod, + @Nullable Boolean onlyMine) { this.page = page; this.index = index; this.projectKey = projectKey; @@ -393,7 +415,8 @@ public class SearchAction implements HotspotsWsAction { this.hotspotKeys = hotspotKeys; this.status = status; this.resolution = resolution; - this.sinceLeakPeriod = sinceLeakPeriod == null ? false : sinceLeakPeriod; + this.sinceLeakPeriod = sinceLeakPeriod != null && sinceLeakPeriod; + this.onlyMine = onlyMine != null && onlyMine; } int getPage() { @@ -431,6 +454,10 @@ public class SearchAction implements HotspotsWsAction { boolean isSinceLeakPeriod() { return sinceLeakPeriod; } + + boolean isOnlyMine() { + return onlyMine; + } } private static final class SearchResponseData { diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/SearchActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/SearchActionTest.java index b2d94ec5d12..2e967221156 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/SearchActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/SearchActionTest.java @@ -71,6 +71,7 @@ import org.sonarqube.ws.Hotspots.Component; import org.sonarqube.ws.Hotspots.SearchWsResponse; import static java.util.Collections.singleton; +import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; @@ -111,8 +112,11 @@ public class SearchActionTest { private WsActionTester actionTester = new WsActionTester(underTest); @Test - public void ws_is_internal() { + public void verify_ws_def() { assertThat(actionTester.getDef().isInternal()).isTrue(); + assertThat(actionTester.getDef().param("onlyMine").isRequired()).isFalse(); + assertThat(actionTester.getDef().param("onlyMine").possibleValues()) + .containsExactlyInAnyOrder("yes", "no", "true", "false"); } @Test @@ -506,6 +510,85 @@ public class SearchActionTest { .containsExactlyInAnyOrder(Arrays.stream(hotspotPR).map(IssueDto::getKey).toArray(String[]::new)); } + @Test + @UseDataProvider("onlyMineParamValues") + public void returns_hotspots_of_specified_project_assigned_to_current_user_if_only_mine_is_set(String onlyMineParameter, boolean shouldFilter) { + ComponentDto project1 = dbTester.components().insertPublicProject(); + String assigneeUuid = this.userSessionRule.logIn().registerComponents(project1).getUuid(); + + indexPermissions(); + ComponentDto file1 = dbTester.components().insertComponent(newFileDto(project1)); + IssueDto[] assigneeHotspots = IntStream.range(0, 1 + RANDOM.nextInt(10)) + .mapToObj(i -> { + RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT); + insertHotspot(project1, file1, rule, randomAlphabetic(5)); + return insertHotspot(project1, file1, rule, assigneeUuid); + }) + .toArray(IssueDto[]::new); + + indexIssues(); + + SearchWsResponse allHotspots = newRequest(project1) + .executeProtobuf(SearchWsResponse.class); + + SearchWsResponse userHotspots = newRequest(project1, r -> r.setParam("onlyMine", onlyMineParameter)) + .executeProtobuf(SearchWsResponse.class); + + assertThat(allHotspots.getHotspotsList()) + .extracting(SearchWsResponse.Hotspot::getKey) + .contains(Arrays.stream(assigneeHotspots).map(IssueDto::getKey).toArray(String[]::new)) + .hasSizeGreaterThan(assigneeHotspots.length); + + if (shouldFilter) { + assertThat(userHotspots.getHotspotsList()) + .extracting(SearchWsResponse.Hotspot::getKey) + .containsOnly(Arrays.stream(assigneeHotspots).map(IssueDto::getKey).toArray(String[]::new)); + } else { + assertThat(userHotspots.getHotspotsList()) + .extracting(SearchWsResponse.Hotspot::getKey) + .containsOnly(allHotspots.getHotspotsList().stream().map(SearchWsResponse.Hotspot::getKey).toArray(String[]::new)); + } + } + + @DataProvider + public static Object[][] onlyMineParamValues() { + return new Object[][] { + {"yes", true}, + {"true", true}, + {"no", false}, + {"false", false} + }; + } + + @Test + public void fail_if_hotspots_provided_with_onlyMine_param() { + ComponentDto project = dbTester.components().insertPrivateProject(); + + userSessionRule.registerComponents(project); + userSessionRule.logIn().addProjectPermission(UserRole.USER, project); + + assertThatThrownBy(() -> actionTester.newRequest() + .setParam("hotspots", IntStream.range(2, 10).mapToObj(String::valueOf).collect(joining(","))) + .setParam("onlyMine", "true") + .execute()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Parameter 'onlyMine' can be used with parameter 'projectKey' only"); + } + + @Test + public void fail_if_user_not_authenticated_with_onlyMine_param() { + ComponentDto project = dbTester.components().insertPublicProject(); + + userSessionRule.anonymous(); + + assertThatThrownBy(() -> actionTester.newRequest() + .setParam("projectKey", project.getKey()) + .setParam("onlyMine", "true") + .execute()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Parameter 'onlyMine' requires user to be logged in"); + } + @Test public void returns_hotpots_with_any_status_if_no_status_nor_resolution_parameter() { ComponentDto project = dbTester.components().insertPublicProject(); @@ -1127,6 +1210,10 @@ public class SearchActionTest { return dbTester.issues().insert(rule, project, file, t -> t.setType(SECURITY_HOTSPOT)); } + private IssueDto insertHotspot(ComponentDto project, ComponentDto file, RuleDefinitionDto rule, String assigneeUuid) { + return dbTester.issues().insert(rule, project, file, t -> t.setType(SECURITY_HOTSPOT).setAssigneeUuid(assigneeUuid)); + } + private TestRequest newRequest(ComponentDto project) { return newRequest(project, null, null); } -- 2.39.5