]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-17393 Add OWASP ASVS report to 'api/security_reports/show' API call.
authorDimitris Kavvathas <dimitris.kavvathas@sonarsource.com>
Thu, 29 Sep 2022 13:00:10 +0000 (15:00 +0200)
committerPhilippe Perrin <philippe.perrin@sonarsource.com>
Fri, 7 Oct 2022 10:13:56 +0000 (12:13 +0200)
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java

index aac88c660fc5d1c5f61896454b0fbb527e30268e..bfabfb5af6a67ba160e5a92c7c9a5ca8ba500cb5 100644 (file)
@@ -68,6 +68,7 @@ import org.joda.time.Duration;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.rules.RuleType;
+import org.sonar.api.server.rule.RulesDefinition.OwaspAsvsVersion;
 import org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version;
 import org.sonar.api.server.rule.RulesDefinition.PciDssVersion;
 import org.sonar.api.utils.DateUtils;
@@ -90,6 +91,7 @@ import org.sonar.server.issue.index.IssueQuery.PeriodStart;
 import org.sonar.server.permission.index.AuthorizationDoc;
 import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
 import org.sonar.server.security.SecurityStandards;
+import org.sonar.server.security.SecurityStandards.OwaspAsvs;
 import org.sonar.server.security.SecurityStandards.PciDss;
 import org.sonar.server.security.SecurityStandards.SQCategory;
 import org.sonar.server.user.UserSession;
@@ -1154,9 +1156,18 @@ public class IssueIndex {
     SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
     Arrays.stream(PciDss.values())
       .forEach(pciDss -> request.aggregation(
-        newPciDssSecurityReportSubAggregations(
-          AggregationBuilders.filter(pciDss.category(), boolQuery().filter(prefixQuery(version.prefix(), pciDss.category() + "."))), version)));
-    return searchPciDss(request, version.label());
+        newSecurityReportSubAggregations(
+          AggregationBuilders.filter(pciDss.category(), boolQuery().filter(prefixQuery(version.prefix(), pciDss.category() + "."))), version.prefix())));
+    return searchWithDistribution(request, version.label());
+  }
+
+  public List<SecurityStandardCategoryStatistics> getOwaspAsvsReport(String projectUuid, boolean isViewOrApp, OwaspAsvsVersion version) {
+    SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
+    Arrays.stream(OwaspAsvs.values())
+      .forEach(owaspAsvs -> request.aggregation(
+        newSecurityReportSubAggregations(
+          AggregationBuilders.filter(owaspAsvs.category(), boolQuery().filter(prefixQuery(version.prefix(), owaspAsvs.category() + "."))), version.prefix())));
+    return searchWithDistribution(request, version.label());
   }
 
   public List<SecurityStandardCategoryStatistics> getOwaspTop10Report(String projectUuid, boolean isViewOrApp, boolean includeCwe, OwaspTop10Version version) {
@@ -1170,12 +1181,12 @@ public class IssueIndex {
     return search(request, includeCwe, version.label());
   }
 
-  private List<SecurityStandardCategoryStatistics> searchPciDss(SearchSourceBuilder sourceBuilder, String version) {
+  private List<SecurityStandardCategoryStatistics> searchWithDistribution(SearchSourceBuilder sourceBuilder, String version) {
     SearchRequest request = EsClient.prepareSearch(TYPE_ISSUE.getMainType())
       .source(sourceBuilder);
     SearchResponse response = client.search(request);
     return response.getAggregations().asList().stream()
-      .map(c -> processPciDssSecurityReportIssueSearchResults((ParsedFilter) c, version))
+      .map(c -> processSecurityReportIssueSearchResultsWithDistribution((ParsedFilter) c, version))
       .collect(MoreCollectors.toList());
   }
 
@@ -1188,7 +1199,7 @@ public class IssueIndex {
       .collect(MoreCollectors.toList());
   }
 
-  private static SecurityStandardCategoryStatistics processPciDssSecurityReportIssueSearchResults(ParsedFilter categoryFilter, String version) {
+  private static SecurityStandardCategoryStatistics processSecurityReportIssueSearchResultsWithDistribution(ParsedFilter categoryFilter, String version) {
     Stream<? extends Terms.Bucket> stream = ((ParsedStringTerms) categoryFilter.getAggregations().get(AGG_DISTRIBUTION)).getBuckets().stream();
     var children = stream.filter(categoryBucket -> StringUtils.startsWith(categoryBucket.getKeyAsString(), categoryFilter.getName() + "."))
       .map(categoryBucket -> processSecurityReportCategorySearchResults(categoryBucket, categoryBucket.getKeyAsString(), null, null)).collect(toList());
@@ -1229,10 +1240,10 @@ public class IssueIndex {
       reviewedSecurityHotspots, securityReviewRating, children, version);
   }
 
-  private static AggregationBuilder newPciDssSecurityReportSubAggregations(AggregationBuilder categoriesAggs, PciDssVersion version) {
+  private static AggregationBuilder newSecurityReportSubAggregations(AggregationBuilder categoriesAggs, String securityStandardVersionPrefix) {
     AggregationBuilder aggregationBuilder = addSecurityReportIssueCountAggregations(categoriesAggs);
     final TermsAggregationBuilder distributionAggregation = AggregationBuilders.terms(AGG_DISTRIBUTION)
-      .field(version.prefix())
+      .field(securityStandardVersionPrefix)
       // 100 should be enough to display all the requirements per category. If not, the UI will be broken anyway
       .size(MAX_FACET_SIZE);
     categoriesAggs.subAggregation(addSecurityReportIssueCountAggregations(distributionAggregation));
index 8a7952c6e1c165388f7ca5105eb5698f6b826699..ba26775b3e971e70d0928428f5da290549848711 100644 (file)
@@ -27,7 +27,6 @@ import org.junit.Test;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.rules.RuleType;
-import org.sonar.api.server.rule.RulesDefinition;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.server.view.index.ViewDoc;
 
@@ -38,6 +37,8 @@ import static java.util.Comparator.comparing;
 import static java.util.stream.Collectors.toList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
+import static org.sonar.api.server.rule.RulesDefinition.OwaspAsvsVersion;
+import static org.sonar.api.server.rule.RulesDefinition.PciDssVersion;
 import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2017;
 import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2021;
 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
@@ -203,6 +204,29 @@ public class IssueIndexSecurityReportsTest extends IssueIndexTestCommon {
     assertThat(pciDss32Report.get(11).getChildren()).isEmpty();
   }
 
+  @Test
+  public void getOwaspAsvs40Report_aggregation() {
+    List<SecurityStandardCategoryStatistics> owaspAsvsReport = indexIssuesAndAssertOwaspAsvsReport();
+
+    assertThat(owaspAsvsReport)
+      .isNotEmpty();
+
+    assertThat(owaspAsvsReport.get(0).getChildren()).hasSize(2);
+    assertThat(owaspAsvsReport.get(1).getChildren()).isEmpty();
+    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(2);
+    assertThat(owaspAsvsReport.get(6).getChildren()).isEmpty();
+    assertThat(owaspAsvsReport.get(7).getChildren()).hasSize(1);
+    assertThat(owaspAsvsReport.get(8).getChildren()).isEmpty();
+    assertThat(owaspAsvsReport.get(9).getChildren()).hasSize(1);
+    assertThat(owaspAsvsReport.get(10).getChildren()).isEmpty();
+    assertThat(owaspAsvsReport.get(11).getChildren()).isEmpty();
+    assertThat(owaspAsvsReport.get(12).getChildren()).isEmpty();
+    assertThat(owaspAsvsReport.get(13).getChildren()).isEmpty();
+  }
+
   @Test
   public void getOwaspTop10Report_aggregation_with_cwe() {
     List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspReport(true);
@@ -300,7 +324,7 @@ public class IssueIndexSecurityReportsTest extends IssueIndexTestCommon {
         .setResolution(Issue.RESOLUTION_FIXED),
       newDoc("notpcidsshotspot", project).setPciDss32(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
 
-    List<SecurityStandardCategoryStatistics> pciDssReport = underTest.getPciDssReport(project.uuid(), false, RulesDefinition.PciDssVersion.V3_2).stream()
+    List<SecurityStandardCategoryStatistics> pciDssReport = underTest.getPciDssReport(project.uuid(), false, PciDssVersion.V3_2).stream()
       .sorted(comparing(s -> parseInt(s.getCategory())))
       .collect(toList());
     assertThat(pciDssReport)
@@ -324,6 +348,49 @@ public class IssueIndexSecurityReportsTest extends IssueIndexTestCommon {
     return pciDssReport;
   }
 
+  private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwaspAsvsReport() {
+    ComponentDto project = newPrivateProjectDto();
+    indexIssues(
+      newDoc("openvul1", project).setOwaspAsvs40(asList("1.2.0", "3.4.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+        .setSeverity(Severity.MAJOR),
+      newDoc("openvul2", project).setOwaspAsvs40(asList("3.3.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+        .setSeverity(Severity.MINOR),
+      newDoc("openvul3", project).setOwaspAsvs40(asList("10.1.2", "6.5")).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("1.3.0", "3.3.2")).setType(RuleType.SECURITY_HOTSPOT)
+        .setStatus(Issue.STATUS_TO_REVIEW),
+      newDoc("toreviewhotspot2", project).setOwaspAsvs40(asList("3.5.6", "6.4.5")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
+      newDoc("reviewedHotspot", project).setOwaspAsvs40(asList("3.1.1", "8.6")).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));
+
+    List<SecurityStandardCategoryStatistics> owaspAsvsReport = underTest.getOwaspAsvsReport(project.uuid(), false, OwaspAsvsVersion.V4_0).stream()
+      .sorted(comparing(s -> parseInt(s.getCategory())))
+      .collect(toList());
+    assertThat(owaspAsvsReport)
+      .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
+        SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
+        SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
+      .containsExactlyInAnyOrder(
+        tuple("1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L, 5),
+        tuple("2", 0L, OptionalInt.empty(), 0L, 0L, 1),
+        tuple("3", 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* reviewedHotspot */, 4),
+        tuple("4", 0L, OptionalInt.empty(), 0L, 0L, 1),
+        tuple("5", 0L, OptionalInt.empty(), 0L, 0L, 1),
+        tuple("6", 2L /* openvul2 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 5),
+        tuple("7", 0L, OptionalInt.empty(), 0L, 0L, 1),
+        tuple("8", 0L, OptionalInt.empty(), 0L, 1L /* reviewedHotspot */, 1),
+        tuple("9", 0L, OptionalInt.empty(), 0L, 0L, 1),
+        tuple("10", 1L, OptionalInt.of(2), 0L, 0L, 1),
+        tuple("11", 0L, OptionalInt.empty(), 0L, 0L, 1),
+        tuple("12", 0L, OptionalInt.empty(), 0L, 0L, 1),
+        tuple("13", 0L, OptionalInt.empty(), 0L, 0L, 1),
+        tuple("14", 0L, OptionalInt.empty(), 0L, 0L, 1));
+
+    return owaspAsvsReport;
+  }
+
   private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwasp2021Report(boolean includeCwe) {
     ComponentDto project = newPrivateProjectDto();
     indexIssues(
@@ -458,7 +525,7 @@ public class IssueIndexSecurityReportsTest extends IssueIndexTestCommon {
     indexView(portfolio1.uuid(), singletonList(project1.uuid()));
     indexView(portfolio2.uuid(), singletonList(project2.uuid()));
 
-    List<SecurityStandardCategoryStatistics> pciDssReport = underTest.getPciDssReport(portfolio1.uuid(), true, RulesDefinition.PciDssVersion.V3_2).stream()
+    List<SecurityStandardCategoryStatistics> pciDssReport = underTest.getPciDssReport(portfolio1.uuid(), true, PciDssVersion.V3_2).stream()
       .sorted(comparing(s -> parseInt(s.getCategory())))
       .collect(toList());
     assertThat(pciDssReport)
@@ -480,6 +547,55 @@ public class IssueIndexSecurityReportsTest extends IssueIndexTestCommon {
         tuple("12", 0L, OptionalInt.empty(), 0L, 0L, 1));
   }
 
+  @Test
+  public void getOwaspAsvsReport_aggregation_on_portfolio() {
+    ComponentDto portfolio1 = db.components().insertPrivateApplication();
+    ComponentDto portfolio2 = db.components().insertPrivateApplication();
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto project2 = db.components().insertPrivateProject();
+
+    indexIssues(
+      newDoc("openvul1", project1).setOwaspAsvs40(asList("1.2.0", "3.4.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+        .setSeverity(Severity.MAJOR),
+      newDoc("openvul2", project2).setOwaspAsvs40(asList("3.3.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+        .setSeverity(Severity.MINOR),
+      newDoc("openvul3", project1).setOwaspAsvs40(asList("10.1.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+        .setSeverity(Severity.MINOR),
+      newDoc("notowaspvul", project1).setOwaspAsvs40(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
+      newDoc("toreviewhotspot1", project2).setOwaspAsvs40(asList("1.3.0", "3.3.2")).setType(RuleType.SECURITY_HOTSPOT)
+        .setStatus(Issue.STATUS_TO_REVIEW),
+      newDoc("toreviewhotspot2", project1).setOwaspAsvs40(asList("3.5.6", "6.4.5")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
+      newDoc("reviewedHotspot", project2).setOwaspAsvs40(asList("3.1.1", "8.6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
+        .setResolution(Issue.RESOLUTION_FIXED),
+      newDoc("notowasphotspot", project1).setOwaspAsvs40(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
+
+    indexView(portfolio1.uuid(), singletonList(project1.uuid()));
+    indexView(portfolio2.uuid(), singletonList(project2.uuid()));
+
+    List<SecurityStandardCategoryStatistics> owaspAsvsReport = underTest.getOwaspAsvsReport(portfolio1.uuid(), true, OwaspAsvsVersion.V4_0).stream()
+      .sorted(comparing(s -> parseInt(s.getCategory())))
+      .collect(toList());
+    assertThat(owaspAsvsReport)
+      .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
+        SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
+        SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
+      .containsExactlyInAnyOrder(
+        tuple("1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
+        tuple("2", 0L, OptionalInt.empty(), 0L, 0L, 1),
+        tuple("3", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L/* toreviewhotspot2 */, 0L, 5),
+        tuple("4", 0L, OptionalInt.empty(), 0L, 0L, 1),
+        tuple("5", 0L, OptionalInt.empty(), 0L, 0L, 1),
+        tuple("6", 1L /* openvul3 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 5),
+        tuple("7", 0L, OptionalInt.empty(), 0L, 0L, 1),
+        tuple("8", 0L, OptionalInt.empty(), 0L, 0L /* reviewedHotspot */, 1),
+        tuple("9", 0L, OptionalInt.empty(), 0L, 0L, 1),
+        tuple("10", 1L /* openvul3 */, OptionalInt.of(2), 0L, 0L, 1),
+        tuple("11", 0L, OptionalInt.empty(), 0L, 0L, 1),
+        tuple("12", 0L, OptionalInt.empty(), 0L, 0L, 1),
+        tuple("13", 0L, OptionalInt.empty(), 0L, 0L, 1),
+        tuple("14", 0L, OptionalInt.empty(), 0L, 0L, 1));
+  }
+
   @Test
   public void getCWETop25Report_aggregation() {
     ComponentDto project = newPrivateProjectDto();
index 1ca2b86ce5c28e8b7f4011b2d0c0afc225fa82f8..63459ace05056de7dc764036b215b08eb08afb15 100644 (file)
@@ -82,6 +82,7 @@ public class IssuesWsParameters {
   public static final String PARAM_PCI_DSS = "pciDss";
   public static final String PARAM_PCI_DSS_32 = "pciDss-3.2";
   public static final String PARAM_PCI_DSS_40 = "pciDss-4.0";
+  public static final String PARAM_OWASP_ASVS = "owaspAsvs";
   public static final String PARAM_OWASP_ASVS_40 = "owaspAsvs-4.0";
   public static final String PARAM_OWASP_TOP_10 = "owaspTop10";
   public static final String PARAM_OWASP_TOP_10_2021 = "owaspTop10-2021";