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;
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;
.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()
}
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<String> projectKey = wsRequest.getProjectKey();
Optional<String> branch = wsRequest.getBranch();
Optional<String> pullRequest = wsRequest.getPullRequest();
Set<String> 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);
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<ComponentDto> getAndValidateProject(DbSession dbSession, WsRequest wsRequest) {
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)));
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<String> 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;
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() {
boolean isSinceLeakPeriod() {
return sinceLeakPeriod;
}
+
+ boolean isOnlyMine() {
+ return onlyMine;
+ }
}
private static final class SearchResponseData {
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;
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
.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();
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);
}