From 3421334ce91e9260738ed5007d0e1396d8a968d5 Mon Sep 17 00:00:00 2001 From: Matteo Mara Date: Fri, 7 Oct 2022 11:35:29 +0200 Subject: [PATCH] SONAR-17404 group Owasp ASVS issues by level in security report PDF --- .../sonar/server/issue/index/IssueIndex.java | 46 +++++++++-- .../index/IssueIndexSecurityReportsTest.java | 82 +++++++++++++++---- 2 files changed, 102 insertions(+), 26 deletions(-) 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 7cffd0d083a..84af82f9e5a 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 @@ -1174,6 +1174,17 @@ public class IssueIndex { return searchWithDistribution(request, version.label(), level); } + public List getOwaspAsvsReportGroupedByLevel(String projectUuid, boolean isViewOrApp, RulesDefinition.OwaspAsvsVersion version, int level) { + SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp); + request.aggregation( + newSecurityReportSubAggregations( + AggregationBuilders.filter( + "l" + level, + boolQuery().filter(termsQuery(version.prefix(), SecurityStandards.OWASP_ASVS_REQUIREMENTS_BY_LEVEL.get(version).get(level)))), + version.prefix())); + return searchWithLevelDistribution(request, version.label(), Integer.toString(level)); + } + public List getOwaspTop10Report(String projectUuid, boolean isViewOrApp, boolean includeCwe, OwaspTop10Version version) { SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp); IntStream.rangeClosed(1, 10).mapToObj(i -> "a" + i) @@ -1185,24 +1196,33 @@ public class IssueIndex { return search(request, includeCwe, version.label()); } + private List searchWithLevelDistribution(SearchSourceBuilder sourceBuilder, String version, @Nullable String level) { + return getSearchResponse(sourceBuilder) + .getAggregations().asList().stream() + .map(c -> processSecurityReportIssueSearchResultsWithLevelDistribution((ParsedFilter) c, version, level)) + .collect(MoreCollectors.toList()); + } + private List searchWithDistribution(SearchSourceBuilder sourceBuilder, String version, @Nullable Integer level) { - SearchRequest request = EsClient.prepareSearch(TYPE_ISSUE.getMainType()) - .source(sourceBuilder); - SearchResponse response = client.search(request); - return response.getAggregations().asList().stream() + return getSearchResponse(sourceBuilder) + .getAggregations().asList().stream() .map(c -> processSecurityReportIssueSearchResultsWithDistribution((ParsedFilter) c, version, level)) .collect(MoreCollectors.toList()); } private List search(SearchSourceBuilder sourceBuilder, boolean includeDistribution, @Nullable String version) { - SearchRequest request = EsClient.prepareSearch(TYPE_ISSUE.getMainType()) - .source(sourceBuilder); - SearchResponse response = client.search(request); - return response.getAggregations().asList().stream() + return getSearchResponse(sourceBuilder) + .getAggregations().asList().stream() .map(c -> processSecurityReportIssueSearchResults((ParsedFilter) c, includeDistribution, version)) .collect(MoreCollectors.toList()); } + private SearchResponse getSearchResponse(SearchSourceBuilder sourceBuilder) { + SearchRequest request = EsClient.prepareSearch(TYPE_ISSUE.getMainType()) + .source(sourceBuilder); + return client.search(request); + } + private static SecurityStandardCategoryStatistics processSecurityReportIssueSearchResultsWithDistribution(ParsedFilter categoryFilter, String version, @Nullable Integer level) { var list = ((ParsedStringTerms) categoryFilter.getAggregations().get(AGG_DISTRIBUTION)).getBuckets(); List children = list.stream() @@ -1214,6 +1234,16 @@ public class IssueIndex { return processSecurityReportCategorySearchResults(categoryFilter, categoryFilter.getName(), children, version); } + private static SecurityStandardCategoryStatistics processSecurityReportIssueSearchResultsWithLevelDistribution(ParsedFilter categoryFilter, String version, String level) { + var list = ((ParsedStringTerms) categoryFilter.getAggregations().get(AGG_DISTRIBUTION)).getBuckets(); + List children = list.stream() + .filter(categoryBucket -> OWASP_ASVS_40_REQUIREMENTS_BY_LEVEL.get(Integer.parseInt(level)).contains(categoryBucket.getKeyAsString())) + .map(categoryBucket -> processSecurityReportCategorySearchResults(categoryBucket, categoryBucket.getKeyAsString(), null, null)) + .collect(toList()); + + return processSecurityReportCategorySearchResults(categoryFilter, categoryFilter.getName(), children, version); + } + private static SecurityStandardCategoryStatistics processSecurityReportIssueSearchResults(ParsedFilter categoryBucket, boolean includeDistribution, String version) { List children = new ArrayList<>(); if (includeDistribution) { diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java index 44ddc775f4a..baeaccf0d79 100644 --- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java +++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java @@ -19,10 +19,12 @@ */ package org.sonar.server.issue.index; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.OptionalInt; import java.util.stream.Collectors; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import org.sonar.api.issue.Issue; import org.sonar.api.rule.Severity; @@ -213,10 +215,10 @@ public class IssueIndexSecurityReportsTest extends IssueIndexTestCommon { assertThat(owaspAsvsReport.get(0).getChildren()).isEmpty(); assertThat(owaspAsvsReport.get(1).getChildren()).hasSize(2); - assertThat(owaspAsvsReport.get(2).getChildren()).hasSize(3); + assertThat(owaspAsvsReport.get(2).getChildren()).hasSize(4); assertThat(owaspAsvsReport.get(3).getChildren()).isEmpty(); assertThat(owaspAsvsReport.get(4).getChildren()).isEmpty(); - assertThat(owaspAsvsReport.get(5).getChildren()).hasSize(1); + assertThat(owaspAsvsReport.get(5).getChildren()).hasSize(2); assertThat(owaspAsvsReport.get(6).getChildren()).hasSize(1); assertThat(owaspAsvsReport.get(7).getChildren()).hasSize(1); assertThat(owaspAsvsReport.get(8).getChildren()).isEmpty(); @@ -227,6 +229,18 @@ public class IssueIndexSecurityReportsTest extends IssueIndexTestCommon { assertThat(owaspAsvsReport.get(13).getChildren()).isEmpty(); } + @Test + public void getOwaspAsvs40ReportGroupedByLevel_aggregation() { + List owaspAsvsReportGroupedByLevel = indexIssuesAndAssertOwaspAsvsReportGroupedByLevel(); + + assertThat(owaspAsvsReportGroupedByLevel) + .isNotEmpty(); + + assertThat(owaspAsvsReportGroupedByLevel.get(0).getChildren()).hasSize(3); + assertThat(owaspAsvsReportGroupedByLevel.get(1).getChildren()).hasSize(7); + assertThat(owaspAsvsReportGroupedByLevel.get(2).getChildren()).hasSize(11); + } + @Test public void getOwaspTop10Report_aggregation_with_cwe() { List owaspTop10Report = indexIssuesAndAssertOwaspReport(true); @@ -349,23 +363,9 @@ public class IssueIndexSecurityReportsTest extends IssueIndexTestCommon { } private List indexIssuesAndAssertOwaspAsvsReport() { - ComponentDto project = newPrivateProjectDto(); - indexIssues( - newDoc("openvul1", project).setOwaspAsvs40(asList("2.1.1", "3.4.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN) - .setSeverity(Severity.MAJOR), - newDoc("openvul2", project).setOwaspAsvs40(asList("3.2.2", "6.2.1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED) - .setSeverity(Severity.MINOR), - newDoc("openvul3", project).setOwaspAsvs40(asList("10.3.1", "6.2.1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED) - .setSeverity(Severity.MINOR), - newDoc("notowaspasvsvul", project).setOwaspAsvs40(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL), - newDoc("toreviewhotspot1", project).setOwaspAsvs40(asList("2.1.2", "3.2.2")).setType(RuleType.SECURITY_HOTSPOT) - .setStatus(Issue.STATUS_TO_REVIEW), - newDoc("toreviewhotspot2", project).setOwaspAsvs40(asList("3.4.5", "7.1.1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW), - newDoc("reviewedHotspot", project).setOwaspAsvs40(asList("3.1.1", "8.3.4")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED) - .setResolution(Issue.RESOLUTION_FIXED), - newDoc("notowaspasvshotspot", project).setOwaspAsvs40(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW)); + ComponentDto project = getProjectWithOwaspAsvsIssuesIndexed(); - List owaspAsvsReport = underTest.getOwaspAsvsReport(project.uuid(), false, OwaspAsvsVersion.V4_0, 1).stream() + List owaspAsvsReport = underTest.getOwaspAsvsReport(project.uuid(), false, OwaspAsvsVersion.V4_0, 3).stream() .sorted(comparing(s -> parseInt(s.getCategory()))) .collect(toList()); assertThat(owaspAsvsReport) @@ -391,6 +391,52 @@ public class IssueIndexSecurityReportsTest extends IssueIndexTestCommon { return owaspAsvsReport; } + private List indexIssuesAndAssertOwaspAsvsReportGroupedByLevel() { + ComponentDto project = getProjectWithOwaspAsvsIssuesIndexed(); + + List owaspAsvsReportGroupedByLevel = new ArrayList<>(); + owaspAsvsReportGroupedByLevel.addAll(underTest.getOwaspAsvsReportGroupedByLevel(project.uuid(), false, OwaspAsvsVersion.V4_0, 1).stream() + .sorted(comparing(s -> parseInt(s.getCategory()))) + .collect(toList())); + owaspAsvsReportGroupedByLevel.addAll(underTest.getOwaspAsvsReportGroupedByLevel(project.uuid(), false, OwaspAsvsVersion.V4_0, 2).stream() + .sorted(comparing(s -> parseInt(s.getCategory()))) + .collect(toList())); + owaspAsvsReportGroupedByLevel.addAll(underTest.getOwaspAsvsReportGroupedByLevel(project.uuid(), false, OwaspAsvsVersion.V4_0, 3).stream() + .sorted(comparing(s -> parseInt(s.getCategory()))) + .collect(toList())); + + assertThat(owaspAsvsReportGroupedByLevel) + .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities, + SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, + SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating) + .containsExactlyInAnyOrder( + tuple("l1", 1L /* openvul2 */, OptionalInt.of(2), 1L /* toreviewhotspot2 */, 0L, 5), + tuple("l2", 2L /* openvul1, openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L /* toreviewhotspot1, toreviewhotspot2 */, 1L /* reviewedHotspot */, 4), + tuple("l3", 3L /* openvul1,openvul2,openvul3 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* reviewedHotspot */, 4)); + + return owaspAsvsReportGroupedByLevel; + } + + @NotNull + private ComponentDto getProjectWithOwaspAsvsIssuesIndexed() { + ComponentDto project = newPrivateProjectDto(); + indexIssues( + newDoc("openvul1", project).setOwaspAsvs40(asList("2.4.1", "3.2.4")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN) + .setSeverity(Severity.MAJOR), + newDoc("openvul2", project).setOwaspAsvs40(asList("3.4.5", "6.2.1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED) + .setSeverity(Severity.MINOR), + newDoc("openvul3", project).setOwaspAsvs40(asList("10.2.4", "6.2.8")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED) + .setSeverity(Severity.MINOR), + newDoc("notowaspasvsvul", project).setOwaspAsvs40(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL), + newDoc("toreviewhotspot1", project).setOwaspAsvs40(asList("2.2.5", "3.2.4")).setType(RuleType.SECURITY_HOTSPOT) + .setStatus(Issue.STATUS_TO_REVIEW), + newDoc("toreviewhotspot2", project).setOwaspAsvs40(asList("3.6.1", "7.1.1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW), + newDoc("reviewedHotspot", project).setOwaspAsvs40(asList("3.3.3", "8.3.7")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED) + .setResolution(Issue.RESOLUTION_FIXED), + newDoc("notowaspasvshotspot", project).setOwaspAsvs40(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW)); + return project; + } + private List indexIssuesAndAssertOwasp2021Report(boolean includeCwe) { ComponentDto project = newPrivateProjectDto(); indexIssues( -- 2.39.5