]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-17393 Add OWASP ASVS filtering to issues search API call
authorZipeng WU <zipeng.wu@sonarsource.com>
Fri, 30 Sep 2022 08:11:08 +0000 (10:11 +0200)
committerPhilippe Perrin <philippe.perrin@sonarsource.com>
Fri, 7 Oct 2022 10:13:56 +0000 (12:13 +0200)
server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java
server/sonar-server-common/src/test/java/org/sonar/server/issue/SearchRequestTest.java
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java

index 108e857766180f6751e75bde8c5ae0bf995901f7..8852d1f2e418cf269792dde0e018a5380106234a 100644 (file)
@@ -64,6 +64,7 @@ public class SearchRequest {
   private List<String> pciDss32;
   private List<String> pciDss40;
   private List<String> owaspTop10;
+  private List<String> owaspAsvs40;
   private List<String> owaspTop10For2021;
   private List<String> sansTop25;
   private List<String> sonarsourceSecurity;
@@ -390,6 +391,16 @@ public class SearchRequest {
     return this;
   }
 
+  @CheckForNull
+  public List<String> getOwaspAsvs40() {
+    return owaspAsvs40;
+  }
+
+  public SearchRequest setOwaspAsvs40(@Nullable List<String> owaspAsvs40) {
+    this.owaspAsvs40 = owaspAsvs40;
+    return this;
+  }
+
   @CheckForNull
   public List<String> getOwaspTop10() {
     return owaspTop10;
index 774c12d609297c80a231675002c543bd543b3ad9..adaeb8a4fcb707e07637a88c7b9816de769bb0f0 100644 (file)
@@ -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");
   }
index bfabfb5af6a67ba160e5a92c7c9a5ca8ba500cb5..093324796ad54e4f5c0068a74cb23f6e43bbdc89 100644 (file)
@@ -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<String> values, AllFilters allFilters) {
+  private static void addSecurityCategoryPrefixFilter(String fieldName, Facet facet, Collection<String> 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<String> facetNames = options.getFacets();
     Set<TopAggregationDefinition<?>> 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);
     }
   }
index f6c2773b80663cfab3b1dc4f4d9d62208d984804..ed9e96a6a7776100224ac3fbe2bac7292a8122ca 100644 (file)
@@ -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())
index 20d86b48c4053bd7e4afdf0fc73293403c4616dc..91061c3adf871ff12c11fb77c984817541a657ee 100644 (file)
@@ -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<String, Long> 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<String, Long> 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<String, Long> 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<String, Long> 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<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
     assertThat(createdAt).containsOnly(
index 36aa53d81cb4f265fa6ffdf7b09f8cd9c70d0a30..e2f508b8bb8d6218db176c8c0118d151acd0c6b5 100644 (file)
@@ -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))
index 293ea18a699bc94b1e265586737d10283b14bf5f..5f8f3e082bdf4e1e0b1c3519518affccc9cbe9b0 100644 (file)
@@ -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<RuleDto> 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<IssueDto> 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);