From: Michal Duda Date: Thu, 1 Oct 2020 15:59:54 +0000 (+0200) Subject: SONAR-13566 Add security standards filters to hotspot search WS X-Git-Tag: 8.6.0.39681~191 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=fd7e12d22fe7e9b1b4928539a50a3c47e193fb10;p=sonarqube.git SONAR-13566 Add security standards filters to hotspot search WS --- diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java index 9ce8c318f2d..ad748b320ca 100644 --- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java +++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java @@ -460,7 +460,7 @@ public class IssueIndex { facet.getFilterScope(), boolQuery() .must(securityCategoryFilter) - .must(termQuery(FIELD_ISSUE_TYPE, VULNERABILITY.name()))); + .must(termsQuery(FIELD_ISSUE_TYPE, VULNERABILITY.name(), SECURITY_HOTSPOT.name()))); } } 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 214184cea9d..41a6b9c46aa 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 @@ -47,7 +47,6 @@ import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.Paging; import org.sonar.api.utils.System2; import org.sonar.api.web.UserRole; -import org.sonar.core.util.stream.MoreCollectors; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.component.ComponentDto; @@ -81,6 +80,9 @@ import static org.sonar.api.utils.DateUtils.longToDate; import static org.sonar.api.utils.Paging.forPageIndex; import static org.sonar.core.util.stream.MoreCollectors.toList; import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; +import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_INSECURE_INTERACTION; +import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_POROUS_DEFENSES; +import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_RISKY_RESOURCE; import static org.sonar.server.security.SecurityStandards.fromSecurityStandards; import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; @@ -98,6 +100,10 @@ public class SearchAction implements HotspotsWsAction { 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 static final String PARAM_OWASP_TOP_10 = "owaspTop10"; + private static final String PARAM_SANS_TOP_25 = "sansTop25"; + private static final String PARAM_SONARSOURCE_SECURITY = "sonarsourceSecurity"; + private static final List STATUSES = ImmutableList.of(STATUS_TO_REVIEW, STATUS_REVIEWED); private final DbClient dbClient; @@ -105,7 +111,7 @@ public class SearchAction implements HotspotsWsAction { private final IssueIndex issueIndex; private final IssueIndexSyncProgressChecker issueIndexSyncProgressChecker; private final HotspotWsResponseFormatter responseFormatter; - private System2 system2; + private final System2 system2; public SearchAction(DbClient dbClient, UserSession userSession, IssueIndex issueIndex, IssueIndexSyncProgressChecker issueIndexSyncProgressChecker, @@ -124,7 +130,7 @@ public class SearchAction implements HotspotsWsAction { .createAction("search") .setHandler(this) .setDescription("Search for Security Hotpots." - + "
When issue indexation is in progress returns 503 service unavailable HTTP code.") + + "
When issue indexation is in progress returns 503 service unavailable HTTP code.") .setSince("8.1") .setInternal(true); @@ -163,6 +169,19 @@ public class SearchAction implements HotspotsWsAction { .setDescription("If 'projectKey' is provided, returns only Security Hotspots assigned to the current user") .setBooleanPossibleValues() .setRequired(false); + action.createParam(PARAM_OWASP_TOP_10) + .setDescription("Comma-separated list of OWASP Top 10 lowercase categories.") + .setSince("8.6") + .setPossibleValues("a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10"); + action.createParam(PARAM_SANS_TOP_25) + .setDescription("Comma-separated list of SANS Top 25 categories.") + .setSince("8.6") + .setPossibleValues(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES); + action.createParam(PARAM_SONARSOURCE_SECURITY) + .setDescription("Comma-separated list of SonarSource security categories. Use '" + SecurityStandards.SQCategory.OTHERS.getKey() + + "' to select issues not associated with any category") + .setSince("8.6") + .setPossibleValues(Arrays.stream(SecurityStandards.SQCategory.values()).map(SecurityStandards.SQCategory::getKey).collect(Collectors.toList())); action.setResponseExample(getClass().getResource("search-example.json")); } @@ -174,7 +193,7 @@ public class SearchAction implements HotspotsWsAction { try (DbSession dbSession = dbClient.openSession(false)) { checkIfNeedIssueSync(dbSession, wsRequest); Optional project = getAndValidateProjectOrApplication(dbSession, wsRequest); - SearchResponseData searchResponseData = searchHotspots(wsRequest, dbSession, project, wsRequest.getHotspotKeys()); + SearchResponseData searchResponseData = searchHotspots(wsRequest, dbSession, project.orElse(null)); loadComponents(dbSession, searchResponseData); loadRules(dbSession, searchResponseData); writeProtobuf(formatResponse(searchResponseData), request, response); @@ -192,15 +211,19 @@ public class SearchAction implements HotspotsWsAction { } private static WsRequest toWsRequest(Request request) { - List hotspotKeysList = request.paramAsStrings(PARAM_HOTSPOTS); - Set hotspotKeys = hotspotKeysList == null ? ImmutableSet.of() : hotspotKeysList.stream().collect(MoreCollectors.toSet(hotspotKeysList.size())); + List hotspotList = request.paramAsStrings(PARAM_HOTSPOTS); + Set hotspotKeys = hotspotList != null ? ImmutableSet.copyOf(hotspotList) : ImmutableSet.of(); + List owaspTop10List = request.paramAsStrings(PARAM_OWASP_TOP_10); + Set owaspTop10 = owaspTop10List != null ? ImmutableSet.copyOf(owaspTop10List) : ImmutableSet.of(); + List sansTop25List = request.paramAsStrings(PARAM_SANS_TOP_25); + Set sansTop25 = sansTop25List != null ? ImmutableSet.copyOf(sansTop25List) : ImmutableSet.of(); + List sonarsourceSecurityList = request.paramAsStrings(PARAM_SONARSOURCE_SECURITY); + Set sonarsourceSecurity = sonarsourceSecurityList != null ? ImmutableSet.copyOf(sonarsourceSecurityList) : ImmutableSet.of(); + return new WsRequest( - request.mandatoryParamAsInt(PAGE), request.mandatoryParamAsInt(PAGE_SIZE), - 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_ONLY_MINE)); + request.mandatoryParamAsInt(PAGE), request.mandatoryParamAsInt(PAGE_SIZE), 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_ONLY_MINE), owaspTop10, sansTop25, sonarsourceSecurity); } private void validateParameters(WsRequest wsRequest) { @@ -244,7 +267,7 @@ public class SearchAction implements HotspotsWsAction { private Optional getAndValidateProjectOrApplication(DbSession dbSession, WsRequest wsRequest) { return wsRequest.getProjectKey().map(projectKey -> { - ComponentDto project = getProject(dbSession, projectKey, wsRequest.getBranch(), wsRequest.getPullRequest()) + ComponentDto project = getProject(dbSession, projectKey, wsRequest.getBranch().orElse(null), wsRequest.getPullRequest().orElse(null)) .filter(t -> Scopes.PROJECT.equals(t.scope()) && SUPPORTED_QUALIFIERS.contains(t.qualifier())) .filter(ComponentDto::isEnabled) .orElseThrow(() -> new NotFoundException(format("Project '%s' not found", projectKey))); @@ -253,17 +276,17 @@ public class SearchAction implements HotspotsWsAction { }); } - private Optional getProject(DbSession dbSession, String projectKey, Optional branch, Optional pullRequest) { - if (branch.isPresent()) { - return dbClient.componentDao().selectByKeyAndBranch(dbSession, projectKey, branch.get()); - } else if (pullRequest.isPresent()) { - return dbClient.componentDao().selectByKeyAndPullRequest(dbSession, projectKey, pullRequest.get()); + private Optional getProject(DbSession dbSession, String projectKey, @Nullable String branch, @Nullable String pullRequest) { + if (branch != null) { + return dbClient.componentDao().selectByKeyAndBranch(dbSession, projectKey, branch); + } else if (pullRequest != null) { + return dbClient.componentDao().selectByKeyAndPullRequest(dbSession, projectKey, pullRequest); } return dbClient.componentDao().selectByKey(dbSession, projectKey); } - private SearchResponseData searchHotspots(WsRequest wsRequest, DbSession dbSession, Optional project, Set hotspotKeys) { - SearchResponse result = doIndexSearch(wsRequest, dbSession, project, hotspotKeys); + private SearchResponseData searchHotspots(WsRequest wsRequest, DbSession dbSession, @Nullable ComponentDto project) { + SearchResponse result = doIndexSearch(wsRequest, dbSession, project); List issueKeys = Arrays.stream(result.getHits().getHits()) .map(SearchHit::getId) .collect(toList(result.getHits().getHits().length)); @@ -286,38 +309,39 @@ public class SearchAction implements HotspotsWsAction { .collect(Collectors.toList()); } - private SearchResponse doIndexSearch(WsRequest wsRequest, DbSession dbSession, Optional project, Set hotspotKeys) { + private SearchResponse doIndexSearch(WsRequest wsRequest, DbSession dbSession, @Nullable ComponentDto project) { IssueQuery.Builder builder = IssueQuery.builder() .types(singleton(RuleType.SECURITY_HOTSPOT.name())) .sort(IssueQuery.SORT_HOTSPOTS) .asc(true) .statuses(wsRequest.getStatus().map(Collections::singletonList).orElse(STATUSES)); - project.ifPresent(p -> { - builder.organizationUuid(p.getOrganizationUuid()); - String projectUuid = firstNonNull(p.getMainBranchProjectUuid(), p.uuid()); - if (Qualifiers.APP.equals(p.qualifier())) { + if (project != null) { + builder.organizationUuid(project.getOrganizationUuid()); + String projectUuid = firstNonNull(project.getMainBranchProjectUuid(), project.uuid()); + if (Qualifiers.APP.equals(project.qualifier())) { builder.viewUuids(singletonList(projectUuid)); } else { builder.projectUuids(singletonList(projectUuid)); } - if (p.getMainBranchProjectUuid() == null) { + if (project.getMainBranchProjectUuid() == null) { builder.mainBranch(true); } else { - builder.branchUuid(p.uuid()); + builder.branchUuid(project.uuid()); builder.mainBranch(false); } if (wsRequest.isSinceLeakPeriod() && !wsRequest.getPullRequest().isPresent()) { - Date sinceDate = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, p.uuid()) + Date sinceDate = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, project.uuid()) .map(s -> longToDate(s.getPeriodDate())) .orElseGet(() -> new Date(system2.now())); builder.createdAfter(sinceDate, false); } - }); - if (!hotspotKeys.isEmpty()) { - builder.issueKeys(hotspotKeys); + } + + if (!wsRequest.getHotspotKeys().isEmpty()) { + builder.issueKeys(wsRequest.getHotspotKeys()); } if (wsRequest.isOnlyMine()) { @@ -327,6 +351,15 @@ public class SearchAction implements HotspotsWsAction { wsRequest.getStatus().ifPresent(status -> builder.resolved(STATUS_REVIEWED.equals(status))); wsRequest.getResolution().ifPresent(resolution -> builder.resolutions(singleton(resolution))); + if (!wsRequest.getOwaspTop10().isEmpty()) { + builder.owaspTop10(wsRequest.getOwaspTop10()); + } + if (!wsRequest.getSansTop25().isEmpty()) { + builder.sansTop25(wsRequest.getSansTop25()); + } + if (!wsRequest.getSonarsourceSecurity().isEmpty()) { + builder.sonarsourceSecurity(wsRequest.getSonarsourceSecurity()); + } IssueQuery query = builder.build(); SearchOptions searchOptions = new SearchOptions() @@ -430,12 +463,15 @@ public class SearchAction implements HotspotsWsAction { private final String resolution; private final boolean sinceLeakPeriod; private final boolean onlyMine; + private final Set owaspTop10; + private final Set sansTop25; + private final Set sonarsourceSecurity; 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 Boolean onlyMine) { + @Nullable Boolean onlyMine, Set owaspTop10, Set sansTop25, Set sonarsourceSecurity) { this.page = page; this.index = index; this.projectKey = projectKey; @@ -446,6 +482,9 @@ public class SearchAction implements HotspotsWsAction { this.resolution = resolution; this.sinceLeakPeriod = sinceLeakPeriod != null && sinceLeakPeriod; this.onlyMine = onlyMine != null && onlyMine; + this.owaspTop10 = owaspTop10; + this.sansTop25 = sansTop25; + this.sonarsourceSecurity = sonarsourceSecurity; } int getPage() { @@ -487,6 +526,18 @@ public class SearchAction implements HotspotsWsAction { boolean isOnlyMine() { return onlyMine; } + + public Set getOwaspTop10() { + return owaspTop10; + } + + public Set getSansTop25() { + return sansTop25; + } + + public Set getSonarsourceSecurity() { + return sonarsourceSecurity; + } } 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 fb8d5e05b67..0ac14278d18 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 @@ -19,7 +19,6 @@ */ package org.sonar.server.hotspot.ws; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; import com.tngtech.java.junit.dataprovider.DataProvider; @@ -43,6 +42,7 @@ import org.junit.runner.RunWith; import org.sonar.api.impl.utils.TestSystem2; import org.sonar.api.issue.Issue; import org.sonar.api.rules.RuleType; +import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.System2; import org.sonar.api.web.UserRole; import org.sonar.db.DbClient; @@ -56,6 +56,7 @@ import org.sonar.db.rule.RuleTesting; import org.sonar.server.es.EsTester; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.issue.index.AsyncIssueIndexing; import org.sonar.server.issue.index.IssueIndex; import org.sonar.server.issue.index.IssueIndexSyncProgressChecker; import org.sonar.server.issue.index.IssueIndexer; @@ -73,6 +74,7 @@ import org.sonarqube.ws.Hotspots; import org.sonarqube.ws.Hotspots.Component; import org.sonarqube.ws.Hotspots.SearchWsResponse; +import static com.google.common.collect.ImmutableSet.of; import static java.util.Collections.singleton; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; @@ -81,9 +83,7 @@ import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -112,26 +112,38 @@ public class SearchActionTest { @Rule public UserSessionRule userSessionRule = UserSessionRule.standalone(); - private TestSystem2 system2 = new TestSystem2(); - private DbClient dbClient = dbTester.getDbClient(); - private TestDefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(dbTester); - - private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule)); - private IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient), null); - private ViewIndexer viewIndexer = new ViewIndexer(dbClient, es.client()); - private PermissionIndexer permissionIndexer = new PermissionIndexer(dbClient, es.client(), issueIndexer); - private HotspotWsResponseFormatter responseFormatter = new HotspotWsResponseFormatter(defaultOrganizationProvider); - private IssueIndexSyncProgressChecker issueIndexSyncProgressChecker = mock(IssueIndexSyncProgressChecker.class); - private SearchAction underTest = new SearchAction(dbClient, userSessionRule, issueIndex, + private final TestSystem2 system2 = new TestSystem2(); + private final DbClient dbClient = dbTester.getDbClient(); + private final TestDefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(dbTester); + private final IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule)); + private final IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient), mock(AsyncIssueIndexing.class)); + private final ViewIndexer viewIndexer = new ViewIndexer(dbClient, es.client()); + private final PermissionIndexer permissionIndexer = new PermissionIndexer(dbClient, es.client(), issueIndexer); + private final HotspotWsResponseFormatter responseFormatter = new HotspotWsResponseFormatter(defaultOrganizationProvider); + private final IssueIndexSyncProgressChecker issueIndexSyncProgressChecker = mock(IssueIndexSyncProgressChecker.class); + private final SearchAction underTest = new SearchAction(dbClient, userSessionRule, issueIndex, issueIndexSyncProgressChecker, responseFormatter, system2); - private WsActionTester actionTester = new WsActionTester(underTest); + private final WsActionTester actionTester = new WsActionTester(underTest); @Test public void verify_ws_def() { + WebService.Param onlyMineParam = actionTester.getDef().param("onlyMine"); + WebService.Param owaspTop10Param = actionTester.getDef().param("owaspTop10"); + WebService.Param sansTop25Param = actionTester.getDef().param("sansTop25"); + WebService.Param sonarsourceSecurityParam = actionTester.getDef().param("sonarsourceSecurity"); + assertThat(actionTester.getDef().isInternal()).isTrue(); - assertThat(actionTester.getDef().param("onlyMine").isRequired()).isFalse(); + assertThat(onlyMineParam).isNotNull(); + assertThat(onlyMineParam.isRequired()).isFalse(); assertThat(actionTester.getDef().param("onlyMine").possibleValues()) .containsExactlyInAnyOrder("yes", "no", "true", "false"); + + assertThat(owaspTop10Param).isNotNull(); + assertThat(owaspTop10Param.isRequired()).isFalse(); + assertThat(sansTop25Param).isNotNull(); + assertThat(sansTop25Param.isRequired()).isFalse(); + assertThat(sonarsourceSecurityParam).isNotNull(); + assertThat(sonarsourceSecurityParam.isRequired()).isFalse(); } @Test @@ -732,12 +744,12 @@ public class SearchActionTest { userSessionRule.registerComponents(project); userSessionRule.logIn().addProjectPermission(UserRole.USER, project); - assertThatThrownBy(() -> actionTester.newRequest() + TestRequest request = 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"); + .setParam("onlyMine", "true"); + assertThatThrownBy(request::execute) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Parameter 'onlyMine' can be used with parameter 'projectKey' only"); } @Test @@ -746,12 +758,12 @@ public class SearchActionTest { userSessionRule.anonymous(); - assertThatThrownBy(() -> actionTester.newRequest() + TestRequest request = actionTester.newRequest() .setParam("projectKey", project.getKey()) - .setParam("onlyMine", "true") - .execute()) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Parameter 'onlyMine' requires user to be logged in"); + .setParam("onlyMine", "true"); + assertThatThrownBy(request::execute) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Parameter 'onlyMine' requires user to be logged in"); } @Test @@ -949,7 +961,7 @@ public class SearchActionTest { }); Stream sqCategoryOTHERS = Stream.of( new Object[] {Collections.emptySet(), SQCategory.OTHERS}, - new Object[] {ImmutableSet.of("foo", "donut", "acme"), SQCategory.OTHERS}); + new Object[] {of("foo", "donut", "acme"), SQCategory.OTHERS}); return Stream.concat(allCategoriesButOTHERS, sqCategoryOTHERS).toArray(Object[][]::new); } @@ -1308,6 +1320,72 @@ public class SearchActionTest { .containsExactlyInAnyOrder(selectedHotspots.stream().map(IssueDto::getKey).toArray(String[]::new)); } + @Test + public void returns_hotspots_with_specified_sonarsourceSecurity_category() { + ComponentDto project = dbTester.components().insertPublicProject(); + userSessionRule.registerComponents(project); + indexPermissions(); + ComponentDto file = dbTester.components().insertComponent(newFileDto(project)); + RuleDefinitionDto rule1 = newRule(SECURITY_HOTSPOT); + RuleDefinitionDto rule2 = newRule(SECURITY_HOTSPOT, r -> r.setSecurityStandards(of("cwe:117", "cwe:190"))); + RuleDefinitionDto rule3 = newRule(SECURITY_HOTSPOT, r -> r.setSecurityStandards(of("owaspTop10:a1", "cwe:601"))); + insertHotspot(project, file, rule1); + IssueDto hotspot2 = insertHotspot(project, file, rule2); + insertHotspot(project, file, rule3); + indexIssues(); + + SearchWsResponse response = newRequest(project).setParam("sonarsourceSecurity", "log-injection") + .executeProtobuf(SearchWsResponse.class); + + assertThat(response.getHotspotsList()) + .extracting(SearchWsResponse.Hotspot::getKey) + .containsExactly(hotspot2.getKey()); + } + + @Test + public void returns_hotspots_with_specified_owaspTop10_category() { + ComponentDto project = dbTester.components().insertPublicProject(); + userSessionRule.registerComponents(project); + indexPermissions(); + ComponentDto file = dbTester.components().insertComponent(newFileDto(project)); + RuleDefinitionDto rule1 = newRule(SECURITY_HOTSPOT); + RuleDefinitionDto rule2 = newRule(SECURITY_HOTSPOT, r -> r.setSecurityStandards(of("cwe:117", "cwe:190"))); + RuleDefinitionDto rule3 = newRule(SECURITY_HOTSPOT, r -> r.setSecurityStandards(of("owaspTop10:a1", "cwe:601"))); + insertHotspot(project, file, rule1); + insertHotspot(project, file, rule2); + IssueDto hotspot3 = insertHotspot(project, file, rule3); + indexIssues(); + + SearchWsResponse response = newRequest(project).setParam("owaspTop10", "a1") + .executeProtobuf(SearchWsResponse.class); + + assertThat(response.getHotspotsList()) + .extracting(SearchWsResponse.Hotspot::getKey) + .containsExactly(hotspot3.getKey()); + } + + @Test + public void returns_hotspots_with_specified_sansTop25_category() { + ComponentDto project = dbTester.components().insertPublicProject(); + userSessionRule.registerComponents(project); + indexPermissions(); + ComponentDto file = dbTester.components().insertComponent(newFileDto(project)); + RuleDefinitionDto rule1 = newRule(SECURITY_HOTSPOT); + RuleDefinitionDto rule2 = newRule(SECURITY_HOTSPOT, r -> r.setSecurityStandards(of("cwe:117", "cwe:190"))); + RuleDefinitionDto rule3 = newRule(SECURITY_HOTSPOT, r -> r.setSecurityStandards(of("owaspTop10:a1", "cwe:601"))); + insertHotspot(project, file, rule1); + insertHotspot(project, file, rule2); + IssueDto hotspot3 = insertHotspot(project, file, rule3); + indexIssues(); + + SearchWsResponse response = newRequest(project).setParam("sansTop25", "insecure-interaction") + .executeProtobuf(SearchWsResponse.class); + + assertThat(response.getHotspotsList()) + .extracting(SearchWsResponse.Hotspot::getKey) + .containsExactly(hotspot3.getKey()); + } + @Test public void returns_hotspots_on_the_leak_period_when_sinceLeakPeriod_is_true() { ComponentDto project = dbTester.components().insertPublicProject(); @@ -1395,7 +1473,7 @@ public class SearchActionTest { } @Test - public void returnsall_issues_when_sinceLeakPeriod_is_true_and_is_pr() { + public void returns_all_issues_when_sinceLeakPeriod_is_true_and_is_pr() { long referenceDate = 800_996_999_332L; system2.setNow(referenceDate + 10_000);