From 7a8d574457de509dd229ae9145d0c9e5276fe35b Mon Sep 17 00:00:00 2001 From: Steve Marion Date: Fri, 30 Sep 2022 14:02:46 +0200 Subject: [PATCH] SONAR-17393 add owasp-4.0 support to hotspot search api --- .../sonar/server/hotspot/ws/SearchAction.java | 173 +++++++++++------- .../server/hotspot/ws/SearchActionTest.java | 35 ++++ 2 files changed, 139 insertions(+), 69 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 6ee7ced5fc8..295de299b02 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 @@ -110,8 +110,10 @@ public class SearchAction implements HotspotsWsAction { private static final String PARAM_PULL_REQUEST = "pullRequest"; private static final String PARAM_IN_NEW_CODE_PERIOD = "inNewCodePeriod"; private static final String PARAM_ONLY_MINE = "onlyMine"; + private static final String PARAM_OWASP_ASVS_LEVEL = "owaspAsvsLevel"; private static final String PARAM_PCI_DSS_32 = "pciDss-3.2"; private static final String PARAM_PCI_DSS_40 = "pciDss-4.0"; + private static final String PARAM_OWASP_ASVS_40 = "owaspAsvs-4.0"; private static final String PARAM_OWASP_TOP_10_2017 = "owaspTop10"; private static final String PARAM_OWASP_TOP_10_2021 = "owaspTop10-2021"; private static final String PARAM_SANS_TOP_25 = "sansTop25"; @@ -130,8 +132,8 @@ public class SearchAction implements HotspotsWsAction { private final System2 system2; public SearchAction(DbClient dbClient, UserSession userSession, IssueIndex issueIndex, - IssueIndexSyncProgressChecker issueIndexSyncProgressChecker, - HotspotWsResponseFormatter responseFormatter, TextRangeResponseFormatter textRangeFormatter, System2 system2) { + IssueIndexSyncProgressChecker issueIndexSyncProgressChecker, HotspotWsResponseFormatter responseFormatter, + TextRangeResponseFormatter textRangeFormatter, System2 system2) { this.dbClient = dbClient; this.userSession = userSession; this.issueIndex = issueIndex; @@ -149,6 +151,7 @@ public class SearchAction implements HotspotsWsAction { Set hotspotKeys = setFromList(request.paramAsStrings(PARAM_HOTSPOTS)); Set pciDss32 = setFromList(request.paramAsStrings(PARAM_PCI_DSS_32)); Set pciDss40 = setFromList(request.paramAsStrings(PARAM_PCI_DSS_40)); + Set owaspAsvs40 = setFromList(request.paramAsStrings(PARAM_OWASP_ASVS_40)); Set owasp2017Top10 = setFromList(request.paramAsStrings(PARAM_OWASP_TOP_10_2017)); Set owasp2021Top10 = setFromList(request.paramAsStrings(PARAM_OWASP_TOP_10_2021)); Set sansTop25 = setFromList(request.paramAsStrings(PARAM_SANS_TOP_25)); @@ -159,8 +162,8 @@ public class SearchAction implements HotspotsWsAction { 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_IN_NEW_CODE_PERIOD), request.paramAsBoolean(PARAM_ONLY_MINE), pciDss32, pciDss40, owasp2017Top10, owasp2021Top10, sansTop25, - sonarsourceSecurity, cwes, files); + request.paramAsBoolean(PARAM_IN_NEW_CODE_PERIOD), request.paramAsBoolean(PARAM_ONLY_MINE), request.paramAsInt(PARAM_OWASP_ASVS_LEVEL), + pciDss32, pciDss40, owaspAsvs40, owasp2017Top10, owasp2021Top10, sansTop25, sonarsourceSecurity, cwes, files); } @Override @@ -187,6 +190,33 @@ public class SearchAction implements HotspotsWsAction { } } + private static void addSecurityStandardFilters(WsRequest wsRequest, IssueQuery.Builder builder) { + if (!wsRequest.getPciDss32().isEmpty()) { + builder.pciDss32(wsRequest.getPciDss32()); + } + if (!wsRequest.getPciDss40().isEmpty()) { + builder.pciDss40(wsRequest.getPciDss40()); + } + if (!wsRequest.getOwaspAsvs40().isEmpty()) { + builder.owaspAsvs40(wsRequest.getOwaspAsvs40()); + } + if (!wsRequest.getOwaspTop10For2017().isEmpty()) { + builder.owaspTop10(wsRequest.getOwaspTop10For2017()); + } + if (!wsRequest.getOwaspTop10For2021().isEmpty()) { + builder.owaspTop10For2021(wsRequest.getOwaspTop10For2021()); + } + if (!wsRequest.getSansTop25().isEmpty()) { + builder.sansTop25(wsRequest.getSansTop25()); + } + if (!wsRequest.getSonarsourceSecurity().isEmpty()) { + builder.sonarsourceSecurity(wsRequest.getSonarsourceSecurity()); + } + if (!wsRequest.getCwe().isEmpty()) { + builder.cwe(wsRequest.getCwe()); + } + } + @Override public void define(WebService.NewController controller) { WebService.NewAction action = controller @@ -235,6 +265,11 @@ public class SearchAction implements HotspotsWsAction { .setBooleanPossibleValues() .setDefaultValue("false") .setSince("9.5"); + action.createParam(PARAM_OWASP_ASVS_LEVEL) + .setDescription("Filters hotspots with lower or equal OWASP ASVS level to the parameter value. Should be used in combination with the 'owaspAsvs-4.0' parameter.") + .setSince("9.7") + .setPossibleValues(1, 2, 3) + .setExampleValue("2"); action.createParam(PARAM_PCI_DSS_32) .setDescription("Comma-separated list of PCI DSS v3.2 categories.") .setSince("9.6") @@ -243,6 +278,10 @@ public class SearchAction implements HotspotsWsAction { .setDescription("Comma-separated list of PCI DSS v4.0 categories.") .setSince("9.6") .setExampleValue("4,6.5.8,10.1"); + action.createParam(PARAM_OWASP_ASVS_40) + .setDescription("Comma-separated list of OWASP ASVS v4.0 categories or rules.") + .setSince("9.7") + .setExampleValue("6,6.1.2"); action.createParam(PARAM_ONLY_MINE) .setDescription("If 'projectKey' is provided, returns only Security Hotspots assigned to the current user") .setBooleanPossibleValues() @@ -276,45 +315,6 @@ public class SearchAction implements HotspotsWsAction { action.setResponseExample(getClass().getResource("search-example.json")); } - 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); - - checkArgument( - branch.isEmpty() || projectKey.isPresent(), - "Parameter '%s' must be used with parameter '%s'", PARAM_BRANCH, PARAM_PROJECT_KEY); - checkArgument( - pullRequest.isEmpty() || projectKey.isPresent(), - "Parameter '%s' must be used with parameter '%s'", PARAM_PULL_REQUEST, PARAM_PROJECT_KEY); - checkArgument( - !(branch.isPresent() && pullRequest.isPresent()), - "Only one of parameters '%s' and '%s' can be provided", PARAM_BRANCH, PARAM_PULL_REQUEST); - - Optional status = wsRequest.getStatus(); - Optional resolution = wsRequest.getResolution(); - checkArgument(status.isEmpty() || hotspotKeys.isEmpty(), - "Parameter '%s' can't be used with parameter '%s'", PARAM_STATUS, PARAM_HOTSPOTS); - checkArgument(resolution.isEmpty() || hotspotKeys.isEmpty(), - "Parameter '%s' can't be used with parameter '%s'", PARAM_RESOLUTION, PARAM_HOTSPOTS); - - resolution.ifPresent( - 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 getAndValidateProjectOrApplication(DbSession dbSession, WsRequest wsRequest) { return wsRequest.getProjectKey().map(projectKey -> { ComponentDto project = getProject(dbSession, projectKey, wsRequest.getBranch().orElse(null), wsRequest.getPullRequest().orElse(null)) @@ -411,28 +411,51 @@ public class SearchAction implements HotspotsWsAction { return issueIndex.search(query, searchOptions); } - private static void addSecurityStandardFilters(WsRequest wsRequest, IssueQuery.Builder builder) { - if (!wsRequest.getPciDss32().isEmpty()) { - builder.pciDss32(wsRequest.getPciDss32()); - } - if (!wsRequest.getPciDss40().isEmpty()) { - builder.pciDss40(wsRequest.getPciDss40()); - } - if (!wsRequest.getOwaspTop10For2017().isEmpty()) { - builder.owaspTop10(wsRequest.getOwaspTop10For2017()); - } - if (!wsRequest.getOwaspTop10For2021().isEmpty()) { - builder.owaspTop10For2021(wsRequest.getOwaspTop10For2021()); - } - if (!wsRequest.getSansTop25().isEmpty()) { - builder.sansTop25(wsRequest.getSansTop25()); - } - if (!wsRequest.getSonarsourceSecurity().isEmpty()) { - builder.sonarsourceSecurity(wsRequest.getSonarsourceSecurity()); - } - if (!wsRequest.getCwe().isEmpty()) { - builder.cwe(wsRequest.getCwe()); + 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); + + checkArgument( + branch.isEmpty() || projectKey.isPresent(), + "Parameter '%s' must be used with parameter '%s'", PARAM_BRANCH, PARAM_PROJECT_KEY); + checkArgument( + pullRequest.isEmpty() || projectKey.isPresent(), + "Parameter '%s' must be used with parameter '%s'", PARAM_PULL_REQUEST, PARAM_PROJECT_KEY); + checkArgument( + !(branch.isPresent() && pullRequest.isPresent()), + "Only one of parameters '%s' and '%s' can be provided", PARAM_BRANCH, PARAM_PULL_REQUEST); + + Optional status = wsRequest.getStatus(); + Optional resolution = wsRequest.getResolution(); + checkArgument(status.isEmpty() || hotspotKeys.isEmpty(), + "Parameter '%s' can't be used with parameter '%s'", PARAM_STATUS, PARAM_HOTSPOTS); + checkArgument(resolution.isEmpty() || hotspotKeys.isEmpty(), + "Parameter '%s' can't be used with parameter '%s'", PARAM_RESOLUTION, PARAM_HOTSPOTS); + + resolution.ifPresent( + 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); } + Set validLevels = Set.of(1, 2, 3); + String levelsStringValue = "{1, 2, 3}"; + wsRequest.getOwaspAsvsLevel().ifPresent( + oal -> checkArgument(validLevels.contains(oal), + "value \"%d\" is not valid for parameter %s. only one of %s is acceptable.", + oal, PARAM_OWASP_ASVS_LEVEL, levelsStringValue) + ); } private static void addMainBranchFilter(@NotNull ComponentDto project, IssueQuery.Builder builder) { @@ -632,8 +655,10 @@ public class SearchAction implements HotspotsWsAction { private final String resolution; private final boolean inNewCodePeriod; private final boolean onlyMine; + private final Integer owaspAsvsLevel; private final Set pciDss32; private final Set pciDss40; + private final Set owaspAsvs40; private final Set owaspTop10For2017; private final Set owaspTop10For2021; private final Set sansTop25; @@ -641,12 +666,12 @@ public class SearchAction implements HotspotsWsAction { private final Set cwe; private final Set files; + 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 inNewCodePeriod, - @Nullable Boolean onlyMine, Set pciDss32, Set pciDss40, Set owaspTop10For2017, Set owaspTop10For2021, Set sansTop25, - Set sonarsourceSecurity, + @Nullable String projectKey, @Nullable String branch, @Nullable String pullRequest, Set hotspotKeys, + @Nullable String status, @Nullable String resolution, @Nullable Boolean inNewCodePeriod, @Nullable Boolean onlyMine, + @Nullable Integer owaspAsvsLevel, Set pciDss32, Set pciDss40, Set owaspAsvs40, + Set owaspTop10For2017, Set owaspTop10For2021, Set sansTop25, Set sonarsourceSecurity, Set cwe, @Nullable Set files) { this.page = page; this.index = index; @@ -658,8 +683,10 @@ public class SearchAction implements HotspotsWsAction { this.resolution = resolution; this.inNewCodePeriod = inNewCodePeriod != null && inNewCodePeriod; this.onlyMine = onlyMine != null && onlyMine; + this.owaspAsvsLevel = owaspAsvsLevel; this.pciDss32 = pciDss32; this.pciDss40 = pciDss40; + this.owaspAsvs40 = owaspAsvs40; this.owaspTop10For2017 = owaspTop10For2017; this.owaspTop10For2021 = owaspTop10For2021; this.sansTop25 = sansTop25; @@ -708,6 +735,10 @@ public class SearchAction implements HotspotsWsAction { return onlyMine; } + public Optional getOwaspAsvsLevel() { + return ofNullable(owaspAsvsLevel); + } + public Set getPciDss32() { return pciDss32; } @@ -716,6 +747,10 @@ public class SearchAction implements HotspotsWsAction { return pciDss40; } + public Set getOwaspAsvs40() { + return owaspAsvs40; + } + public Set getOwaspTop10For2017() { return owaspTop10For2017; } 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 858f3854660..b54499c50ba 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 @@ -124,6 +124,7 @@ public class SearchActionTest { private static final String PARAM_ONLY_MINE = "onlyMine"; private static final String PARAM_PCI_DSS_32 = "pciDss-3.2"; private static final String PARAM_PCI_DSS_40 = "pciDss-4.0"; + private static final String PARAM_OWASP_ASVS_40 = "owaspAsvs-4.0"; private static final String PARAM_OWASP_TOP_10_2017 = "owaspTop10"; private static final String PARAM_OWASP_TOP_10_2021 = "owaspTop10-2021"; private static final String PARAM_SANS_TOP_25 = "sansTop25"; @@ -159,6 +160,7 @@ public class SearchActionTest { WebService.Param onlyMineParam = actionTester.getDef().param(PARAM_ONLY_MINE); WebService.Param pciDss32Param = actionTester.getDef().param(PARAM_PCI_DSS_32); WebService.Param pciDss40Param = actionTester.getDef().param(PARAM_PCI_DSS_40); + WebService.Param owasAsvs40Param = actionTester.getDef().param(PARAM_OWASP_ASVS_40); WebService.Param owaspTop10Param = actionTester.getDef().param(PARAM_OWASP_TOP_10_2017); WebService.Param sansTop25Param = actionTester.getDef().param(PARAM_SANS_TOP_25); WebService.Param sonarsourceSecurityParam = actionTester.getDef().param(PARAM_SONARSOURCE_SECURITY); @@ -174,6 +176,8 @@ public class SearchActionTest { assertThat(pciDss32Param.isRequired()).isFalse(); assertThat(pciDss40Param).isNotNull(); assertThat(pciDss40Param.isRequired()).isFalse(); + assertThat(owasAsvs40Param).isNotNull(); + assertThat(owasAsvs40Param.isRequired()).isFalse(); assertThat(owaspTop10Param).isNotNull(); assertThat(owaspTop10Param.isRequired()).isFalse(); assertThat(sansTop25Param).isNotNull(); @@ -1495,6 +1499,37 @@ public class SearchActionTest { .containsExactly(hotspot4.getKey()); } + @Test + public void returns_hotspots_with_specified_owaspAsvs_category() { + ComponentDto project = dbTester.components().insertPublicProject(); + userSessionRule.registerComponents(project); + indexPermissions(); + ComponentDto file = dbTester.components().insertComponent(newFileDto(project)); + RuleDto rule1 = newRule(SECURITY_HOTSPOT); + RuleDto rule2 = newRule(SECURITY_HOTSPOT, r -> r.setSecurityStandards(of("cwe:117", "cwe:190"))); + RuleDto rule3 = newRule(SECURITY_HOTSPOT, r -> r.setSecurityStandards(of("owaspAsvs-4.0:1.2.3"))); + RuleDto rule4 = newRule(SECURITY_HOTSPOT, r -> r.setSecurityStandards(of("owaspAsvs-4.0:1.2.4"))); + insertHotspot(project, file, rule1); + insertHotspot(project, file, rule2); + IssueDto hotspot3 = insertHotspot(project, file, rule3); + IssueDto hotspot4 = insertHotspot(project, file, rule4); + indexIssues(); + + SearchWsResponse responseFor1 = newRequest(project).setParam(PARAM_OWASP_ASVS_40, "1") + .executeProtobuf(SearchWsResponse.class); + + assertThat(responseFor1.getHotspotsList()) + .extracting(SearchWsResponse.Hotspot::getKey) + .containsOnly(hotspot3.getKey(), hotspot4.getKey()); + + SearchWsResponse responseFor124 = newRequest(project).setParam(PARAM_OWASP_ASVS_40, "1.2.4") + .executeProtobuf(SearchWsResponse.class); + + assertThat(responseFor124.getHotspotsList()) + .extracting(SearchWsResponse.Hotspot::getKey) + .containsExactly(hotspot4.getKey()); + } + @Test public void returns_hotspots_with_specified_owasp2021Top10_category() { ComponentDto project = dbTester.components().insertPublicProject(); -- 2.39.5