From: Zipeng WU Date: Fri, 30 Sep 2022 08:11:08 +0000 (+0200) Subject: SONAR-17393 Add OWASP ASVS filtering to issues search API call X-Git-Tag: 9.7.0.61563~119 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=1942fe88ebad22640e16b21a1896eca094f7a6f7;p=sonarqube.git SONAR-17393 Add OWASP ASVS filtering to issues search API call --- diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java index 108e8577661..8852d1f2e41 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java @@ -64,6 +64,7 @@ public class SearchRequest { private List pciDss32; private List pciDss40; private List owaspTop10; + private List owaspAsvs40; private List owaspTop10For2021; private List sansTop25; private List sonarsourceSecurity; @@ -390,6 +391,16 @@ public class SearchRequest { return this; } + @CheckForNull + public List getOwaspAsvs40() { + return owaspAsvs40; + } + + public SearchRequest setOwaspAsvs40(@Nullable List owaspAsvs40) { + this.owaspAsvs40 = owaspAsvs40; + return this; + } + @CheckForNull public List getOwaspTop10() { return owaspTop10; diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/SearchRequestTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/SearchRequestTest.java index 774c12d6092..adaeb8a4fcb 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/SearchRequestTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/SearchRequestTest.java @@ -50,6 +50,7 @@ public class SearchRequestTest { .setAsc(true) .setInNewCodePeriod(true) .setOwaspTop10For2021(asList("a2", "a3")) + .setOwaspAsvs40(asList("1.1.1", "4.2.2")) .setPciDss32(asList("1", "4")) .setPciDss40(asList("3", "5")); @@ -73,6 +74,7 @@ public class SearchRequestTest { assertThat(underTest.getAsc()).isTrue(); assertThat(underTest.getInNewCodePeriod()).isTrue(); assertThat(underTest.getOwaspTop10For2021()).containsExactly("a2", "a3"); + assertThat(underTest.getOwaspAsvs40()).containsExactly("1.1.1", "4.2.2"); assertThat(underTest.getPciDss32()).containsExactly("1", "4"); assertThat(underTest.getPciDss40()).containsExactly("3", "5"); } 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 bfabfb5af6a..093324796ad 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 @@ -461,8 +461,9 @@ public class IssueIndex { filters.addFilter(FIELD_ISSUE_STATUS, STATUSES.getFilterScope(), createTermsFilter(FIELD_ISSUE_STATUS, query.statuses())); // security category - addPciDssSecurityCategoryFilter(FIELD_ISSUE_PCI_DSS_32, PCI_DSS_32, query.pciDss32(), filters); - addPciDssSecurityCategoryFilter(FIELD_ISSUE_PCI_DSS_40, PCI_DSS_40, query.pciDss40(), filters); + addSecurityCategoryPrefixFilter(FIELD_ISSUE_PCI_DSS_32, PCI_DSS_32, query.pciDss32(), filters); + addSecurityCategoryPrefixFilter(FIELD_ISSUE_PCI_DSS_40, PCI_DSS_40, query.pciDss40(), filters); + addSecurityCategoryPrefixFilter(FIELD_ISSUE_OWASP_ASVS_40, OWASP_ASVS_40, query.owaspAsvs40(), filters); addSecurityCategoryFilter(FIELD_ISSUE_OWASP_TOP_10, OWASP_TOP_10, query.owaspTop10(), filters); addSecurityCategoryFilter(FIELD_ISSUE_OWASP_TOP_10_2021, OWASP_TOP_10_2021, query.owaspTop10For2021(), filters); addSecurityCategoryFilter(FIELD_ISSUE_SANS_TOP_25, SANS_TOP_25, query.sansTop25(), filters); @@ -511,7 +512,7 @@ public class IssueIndex { * @param values The PCI DSS categories to search for * @param allFilters Object that holds all the filters for the Elastic search call */ - private static void addPciDssSecurityCategoryFilter(String fieldName, Facet facet, Collection values, AllFilters allFilters) { + private static void addSecurityCategoryPrefixFilter(String fieldName, Facet facet, Collection values, AllFilters allFilters) { if (values.isEmpty()) { return; } @@ -522,7 +523,7 @@ public class IssueIndex { // the field type must be vulnerability or security hotspot .must(termsQuery(FIELD_ISSUE_TYPE, VULNERABILITY.name(), SECURITY_HOTSPOT.name())); // for top level categories a prefix query is added, while for subcategories a term query is used for exact matching - values.stream().map(v -> choosePciDssQuery(fieldName, v)).forEach(boolQueryBuilder::should); + values.stream().map(v -> choosePrefixQuery(fieldName, v)).forEach(boolQueryBuilder::should); allFilters.addFilter( fieldName, @@ -530,7 +531,7 @@ public class IssueIndex { boolQueryBuilder); } - private static QueryBuilder choosePciDssQuery(String fieldName, String value) { + private static QueryBuilder choosePrefixQuery(String fieldName, String value) { return value.contains(".") ? createTermFilter(fieldName, value) : createPrefixFilter(fieldName, value + "."); } @@ -626,11 +627,11 @@ public class IssueIndex { private static RequestFiltersComputer newFilterComputer(SearchOptions options, AllFilters allFilters) { Collection facetNames = options.getFacets(); Set> facets = Stream.concat( - Stream.of(EFFORT_TOP_AGGREGATION), - facetNames.stream() - .map(FACETS_BY_NAME::get) - .filter(Objects::nonNull) - .map(Facet::getTopAggregationDef)) + Stream.of(EFFORT_TOP_AGGREGATION), + facetNames.stream() + .map(FACETS_BY_NAME::get) + .filter(Objects::nonNull) + .map(Facet::getTopAggregationDef)) .collect(MoreCollectors.toSet(facetNames.size())); return new RequestFiltersComputer(allFilters, facets); @@ -835,11 +836,11 @@ public class IssueIndex { RESOLUTIONS.getName(), RESOLUTIONS.getTopAggregationDef(), RESOLUTIONS.getNumberOfTerms(), NO_EXTRA_FILTER, t -> - // add aggregation of type "missing" to return count of unresolved issues in the facet - t.subAggregation( - addEffortAggregationIfNeeded(query, AggregationBuilders - .missing(RESOLUTIONS.getName() + FACET_SUFFIX_MISSING) - .field(RESOLUTIONS.getFieldName())))); + // add aggregation of type "missing" to return count of unresolved issues in the facet + t.subAggregation( + addEffortAggregationIfNeeded(query, AggregationBuilders + .missing(RESOLUTIONS.getName() + FACET_SUFFIX_MISSING) + .field(RESOLUTIONS.getFieldName())))); esRequest.aggregation(aggregation); } @@ -959,10 +960,10 @@ public class IssueIndex { ASSIGNED_TO_ME.getNumberOfTerms(), NO_EXTRA_FILTER, t -> - // add sub-aggregation to return issue count for current user - aggregationHelper.getSubAggregationHelper() - .buildSelectedItemsAggregation(ASSIGNED_TO_ME.getName(), ASSIGNED_TO_ME.getTopAggregationDef(), new String[] {uuid}) - .ifPresent(t::subAggregation)); + // add sub-aggregation to return issue count for current user + aggregationHelper.getSubAggregationHelper() + .buildSelectedItemsAggregation(ASSIGNED_TO_ME.getName(), ASSIGNED_TO_ME.getTopAggregationDef(), new String[] {uuid}) + .ifPresent(t::subAggregation)); esRequest.aggregation(aggregation); } } diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java index f6c2773b806..ed9e96a6a77 100644 --- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java +++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java @@ -139,6 +139,7 @@ public class IssueQueryFactory { .types(request.getTypes()) .pciDss32(request.getPciDss32()) .pciDss40(request.getPciDss40()) + .owaspAsvs40(request.getOwaspAsvs40()) .owaspTop10(request.getOwaspTop10()) .owaspTop10For2021(request.getOwaspTop10For2021()) .sansTop25(request.getSansTop25()) diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java index 20d86b48c40..91061c3adf8 100644 --- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java +++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java @@ -26,6 +26,7 @@ import java.util.Map; import org.elasticsearch.action.search.SearchResponse; import org.junit.Test; import org.sonar.api.rules.RuleType; +import org.sonar.api.server.rule.RulesDefinition.OwaspAsvsVersion; import org.sonar.db.component.ComponentDto; import org.sonar.db.rule.RuleDto; import org.sonar.server.es.Facets; @@ -193,6 +194,22 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon { entry("3", 1L)); } + @Test + public void facets_on_owaspAsvs40() { + ComponentDto project = newPrivateProjectDto(); + ComponentDto file = newFileDto(project, null); + + indexIssues( + newDoc("I1", file).setType(RuleType.VULNERABILITY).setOwaspAsvs40(asList("1", "2")), + newDoc("I2", file).setType(RuleType.VULNERABILITY).setOwaspAsvs40(singletonList("3")), + newDoc("I3", file)); + + assertThatFacetHasOnly(IssueQuery.builder(), OwaspAsvsVersion.V4_0.prefix(), + entry("1", 1L), + entry("2", 1L), + entry("3", 1L)); + } + @Test public void facets_on_owaspTop10() { ComponentDto project = newPrivateProjectDto(); @@ -521,8 +538,8 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon { SearchOptions options = fixtureForCreatedAtFacet(); SearchResponse result = underTest.search(IssueQuery.builder() - .createdAfter(parseDateTime("2014-09-01T00:00:00+0100")) - .createdBefore(parseDateTime("2014-09-21T00:00:00+0100")).build(), + .createdAfter(parseDateTime("2014-09-01T00:00:00+0100")) + .createdBefore(parseDateTime("2014-09-21T00:00:00+0100")).build(), options); Map createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt"); assertThat(createdAt).containsOnly( @@ -537,8 +554,8 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon { SearchOptions options = fixtureForCreatedAtFacet(); SearchResponse result = underTest.search(IssueQuery.builder() - .createdAfter(parseDateTime("2014-09-01T00:00:00+0100")) - .createdBefore(parseDateTime("2015-01-19T00:00:00+0100")).build(), + .createdAfter(parseDateTime("2014-09-01T00:00:00+0100")) + .createdBefore(parseDateTime("2015-01-19T00:00:00+0100")).build(), options); Map createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt"); assertThat(createdAt).containsOnly( @@ -555,8 +572,8 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon { SearchOptions options = fixtureForCreatedAtFacet(); SearchResponse result = underTest.search(IssueQuery.builder() - .createdAfter(parseDateTime("2011-01-01T00:00:00+0100")) - .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(), + .createdAfter(parseDateTime("2011-01-01T00:00:00+0100")) + .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(), options); Map createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt"); assertThat(createdAt).containsOnly( @@ -573,8 +590,8 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon { SearchOptions options = fixtureForCreatedAtFacet(); SearchResponse result = underTest.search(IssueQuery.builder() - .createdAfter(parseDateTime("2014-09-01T00:00:00-0100")) - .createdBefore(parseDateTime("2014-09-02T00:00:00-0100")).build(), + .createdAfter(parseDateTime("2014-09-01T00:00:00-0100")) + .createdBefore(parseDateTime("2014-09-02T00:00:00-0100")).build(), options); Map createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt"); assertThat(createdAt).containsOnly( @@ -606,7 +623,7 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon { SearchOptions searchOptions = fixtureForCreatedAtFacet(); SearchResponse result = underTest.search(IssueQuery.builder() - .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(), + .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(), searchOptions); Map createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt"); assertThat(createdAt).containsOnly( diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java index 36aa53d81cb..e2f508b8bb8 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java @@ -110,6 +110,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IN_NEW_CODE import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUES; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_LANGUAGES; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ON_COMPONENT_ONLY; +import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_ASVS_40; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10_2021; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PCI_DSS_32; @@ -151,6 +152,7 @@ public class SearchAction implements IssuesWsAction { PARAM_TYPES, PARAM_PCI_DSS_32, PARAM_PCI_DSS_40, + PARAM_OWASP_ASVS_40, PARAM_OWASP_TOP_10, PARAM_OWASP_TOP_10_2021, PARAM_SANS_TOP_25, @@ -277,6 +279,10 @@ public class SearchAction implements IssuesWsAction { .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.") + .setSince("9.7") + .setExampleValue("6,10.1.1"); action.createParam(PARAM_OWASP_TOP_10) .setDescription("Comma-separated list of OWASP Top 10 2017 lowercase categories.") .setSince("7.3") @@ -484,6 +490,7 @@ public class SearchAction implements IssuesWsAction { addMandatoryValuesToFacet(facets, PARAM_PCI_DSS_32, request.getPciDss32()); addMandatoryValuesToFacet(facets, PARAM_PCI_DSS_40, request.getPciDss40()); + addMandatoryValuesToFacet(facets, PARAM_OWASP_ASVS_40, request.getOwaspAsvs40()); addMandatoryValuesToFacet(facets, PARAM_OWASP_TOP_10, request.getOwaspTop10()); addMandatoryValuesToFacet(facets, PARAM_OWASP_TOP_10_2021, request.getOwaspTop10For2021()); addMandatoryValuesToFacet(facets, PARAM_SANS_TOP_25, request.getSansTop25()); @@ -560,6 +567,7 @@ public class SearchAction implements IssuesWsAction { .setTypes(allRuleTypesExceptHotspotsIfEmpty(request.paramAsStrings(PARAM_TYPES))) .setPciDss32(request.paramAsStrings(PARAM_PCI_DSS_32)) .setPciDss40(request.paramAsStrings(PARAM_PCI_DSS_40)) + .setOwaspAsvs40(request.paramAsStrings(PARAM_OWASP_ASVS_40)) .setOwaspTop10(request.paramAsStrings(PARAM_OWASP_TOP_10)) .setOwaspTop10For2021(request.paramAsStrings(PARAM_OWASP_TOP_10_2021)) .setSansTop25(request.paramAsStrings(PARAM_SANS_TOP_25)) diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java index 293ea18a699..5f8f3e082bd 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java @@ -1055,6 +1055,39 @@ public class SearchActionTest { .containsExactly("82fd47d4-b650-4037-80bc-7b112bd4eac3", "82fd47d4-b650-4037-80bc-7b112bd4eac1", "82fd47d4-b650-4037-80bc-7b112bd4eac2"); } + @Test + public void only_vulnerabilities_are_returned_by_owaspAsvs40() { + ComponentDto project = db.components().insertPublicProject(); + ComponentDto file = db.components().insertComponent(newFileDto(project)); + Consumer ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto + .setSecurityStandards(Sets.newHashSet("cwe:20", "owaspTop10:a1", "pciDss-3.2:6.5.3", "owaspAsvs-4.0:12.3.1")) + .setSystemTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); + Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); + RuleDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); + db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); + RuleDto issueRule = db.rules().insertIssueRule(ruleConsumer); + IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto3 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(CODE_SMELL)); + indexPermissionsAndIssues(); + + SearchWsResponse result = ws.newRequest() + .setParam("owaspAsvs-4.0", "12.3.1") + .executeProtobuf(SearchWsResponse.class); + + assertThat(result.getIssuesList()) + .extracting(Issue::getKey) + .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey()); + + result = ws.newRequest() + .setParam("owaspAsvs-4.0", "12") + .executeProtobuf(SearchWsResponse.class); + + assertThat(result.getIssuesList()) + .extracting(Issue::getKey) + .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey()); + } + @Test public void only_vulnerabilities_are_returned_by_pciDss32() { ComponentDto project = db.components().insertPublicProject(); @@ -1692,7 +1725,7 @@ public class SearchActionTest { assertThat(def.params()).extracting("key").containsExactlyInAnyOrder( "additionalFields", "asc", "assigned", "assignees", "author", "componentKeys", "branch", "pullRequest", "createdAfter", "createdAt", "createdBefore", "createdInLast", "directories", "facets", "files", "issues", "scopes", "languages", "onComponentOnly", - "p", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "sinceLeakPeriod", "statuses", "tags", "types", "pciDss-3.2", "pciDss-4.0", "owaspTop10", + "p", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "sinceLeakPeriod", "statuses", "tags", "types", "pciDss-3.2", "pciDss-4.0", "owaspAsvs-4.0", "owaspTop10", "owaspTop10-2021", "sansTop25", "cwe", "sonarsourceSecurity", "timeZone", "inNewCodePeriod"); WebService.Param branch = def.param(PARAM_BRANCH);