]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-17061 Add PCI DSS parameter and facet in 'api/issues/search' API endpoint
authorDimitris Kavvathas <dimitris.kavvathas@sonarsource.com>
Fri, 22 Jul 2022 08:24:21 +0000 (10:24 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 25 Jul 2022 20:03:58 +0000 (20:03 +0000)
16 files changed:
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/IssueIndexDebtTest.java
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexProjectStatisticsTest.java
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityCategoriesTest.java [new file with mode: 0644]
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityHotspotsTest.java
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSortTest.java
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexTestCommon.java [new file with mode: 0644]
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 bc1273d762ee435465852311e5983fdc8e2b6a78..108e857766180f6751e75bde8c5ae0bf995901f7 100644 (file)
@@ -61,6 +61,8 @@ public class SearchRequest {
   private List<String> statuses;
   private List<String> tags;
   private Set<String> types;
+  private List<String> pciDss32;
+  private List<String> pciDss40;
   private List<String> owaspTop10;
   private List<String> owaspTop10For2021;
   private List<String> sansTop25;
@@ -368,6 +370,26 @@ public class SearchRequest {
     return this;
   }
 
+  @CheckForNull
+  public List<String> getPciDss32() {
+    return pciDss32;
+  }
+
+  public SearchRequest setPciDss32(@Nullable List<String> pciDss32) {
+    this.pciDss32 = pciDss32;
+    return this;
+  }
+
+  @CheckForNull
+  public List<String> getPciDss40() {
+    return pciDss40;
+  }
+
+  public SearchRequest setPciDss40(@Nullable List<String> pciDss40) {
+    this.pciDss40 = pciDss40;
+    return this;
+  }
+
   @CheckForNull
   public List<String> getOwaspTop10() {
     return owaspTop10;
index 61136ff96e6ba8a1684b5057748fbcc3dc1c25e8..774c12d609297c80a231675002c543bd543b3ad9 100644 (file)
@@ -49,7 +49,9 @@ public class SearchRequestTest {
       .setSort("CREATION_DATE")
       .setAsc(true)
       .setInNewCodePeriod(true)
-      .setOwaspTop10For2021(asList("a2", "a3"));
+      .setOwaspTop10For2021(asList("a2", "a3"))
+      .setPciDss32(asList("1", "4"))
+      .setPciDss40(asList("3", "5"));
 
     assertThat(underTest.getIssues()).containsOnlyOnce("anIssueKey");
     assertThat(underTest.getSeverities()).containsExactly("MAJOR", "MINOR");
@@ -71,6 +73,8 @@ public class SearchRequestTest {
     assertThat(underTest.getAsc()).isTrue();
     assertThat(underTest.getInNewCodePeriod()).isTrue();
     assertThat(underTest.getOwaspTop10For2021()).containsExactly("a2", "a3");
+    assertThat(underTest.getPciDss32()).containsExactly("1", "4");
+    assertThat(underTest.getPciDss40()).containsExactly("3", "5");
   }
 
   @Test
index d9c53eed857755668274c4e8c11bfef4a02f407a..2ed15da00f5eaae21dfa2d7effeb21a0ddf7f084 100644 (file)
@@ -455,8 +455,8 @@ public class IssueIndex {
     filters.addFilter(FIELD_ISSUE_STATUS, STATUSES.getFilterScope(), createTermsFilter(FIELD_ISSUE_STATUS, query.statuses()));
 
     // security category
-    addSecurityCategoryFilter(FIELD_ISSUE_PCI_DSS_32, PCI_DSS_32, query.pciDss32(), filters);
-    addSecurityCategoryFilter(FIELD_ISSUE_PCI_DSS_40, PCI_DSS_40, query.pciDss40(), filters);
+    addPciDssSecurityCategoryFilter(FIELD_ISSUE_PCI_DSS_32, PCI_DSS_32, query.pciDss32(), filters);
+    addPciDssSecurityCategoryFilter(FIELD_ISSUE_PCI_DSS_40, PCI_DSS_40, query.pciDss40(), 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);
@@ -485,6 +485,49 @@ public class IssueIndex {
     }
   }
 
+  /**
+   * <p>Builds the Elasticsearch boolean query to filter the PCI DSS categories.</p>
+   *
+   * <p>The PCI DSS security report handles all the subcategories as one level. This means that subcategory 1.1 doesn't include the issues from 1.1.1.
+   * Taking this into account, the search filter follows the same logic and uses prefix matching for top-level categories and exact matching for subcategories</p>
+   *
+   * <p>Example</p>
+   * <p>List of PCI DSS categories in issues: {1.5.8, 1.5.9, 1.6.7}
+   *   <ul>
+   *     <li>Search: {1}, returns {1.5.8, 1.5.9, 1.6.7}</li>
+   *     <li>Search: {1.5.8}, returns {1.5.8}</li>
+   *     <li>Search: {1.5}, returns {}</li>
+   *   </ul>
+   * </p>
+   *
+   * @param fieldName The PCI DSS version, e.g. pciDss-3.2
+   * @param facet The facet used for the filter
+   * @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) {
+    if (values.isEmpty()) {
+      return;
+    }
+
+    BoolQueryBuilder boolQueryBuilder = boolQuery()
+      // ensures that at least one "should" query is matched. Without it, "should" queries are optional, when a "must" is also present.
+      .minimumShouldMatch(1)
+      // 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);
+
+    allFilters.addFilter(
+      fieldName,
+      facet.getFilterScope(),
+      boolQueryBuilder);
+  }
+
+  private static QueryBuilder choosePciDssQuery(String fieldName, String value) {
+    return value.contains(".") ? createTermFilter(fieldName, value) : createPrefixFilter(fieldName, value + ".");
+  }
+
   private static void addSeverityFilter(IssueQuery query, AllFilters allFilters) {
     QueryBuilder severityFieldFilter = createTermsFilter(FIELD_ISSUE_SEVERITY, query.severities());
     if (severityFieldFilter != null) {
@@ -615,6 +658,10 @@ public class IssueIndex {
     return value == null ? null : termQuery(field, value);
   }
 
+  private static QueryBuilder createPrefixFilter(String field, String value) {
+    return prefixQuery(field, value);
+  }
+
   private void configureSorting(IssueQuery query, SearchSourceBuilder esRequest) {
     createSortBuilders(query).forEach(esRequest::sort);
   }
@@ -715,6 +762,8 @@ public class IssueIndex {
     addFacetIfNeeded(options, aggregationHelper, esRequest, TAGS, query.tags().toArray());
     addFacetIfNeeded(options, aggregationHelper, esRequest, TYPES, query.types().toArray());
 
+    addSecurityCategoryFacetIfNeeded(PARAM_PCI_DSS_32, PCI_DSS_32, options, aggregationHelper, esRequest, query.pciDss32().toArray());
+    addSecurityCategoryFacetIfNeeded(PARAM_PCI_DSS_40, PCI_DSS_40, options, aggregationHelper, esRequest, query.pciDss40().toArray());
     addSecurityCategoryFacetIfNeeded(PARAM_OWASP_TOP_10, OWASP_TOP_10, options, aggregationHelper, esRequest, query.owaspTop10().toArray());
     addSecurityCategoryFacetIfNeeded(PARAM_OWASP_TOP_10_2021, OWASP_TOP_10_2021, options, aggregationHelper, esRequest, query.owaspTop10For2021().toArray());
     addSecurityCategoryFacetIfNeeded(PARAM_SANS_TOP_25, SANS_TOP_25, options, aggregationHelper, esRequest, query.sansTop25().toArray());
@@ -1003,7 +1052,7 @@ public class IssueIndex {
     requestBuilder.source(sourceBuilder);
     SearchResponse response = client.search(requestBuilder);
     return response.getAggregations().asList().stream()
-      .map(x -> (ParsedFilter) x)
+      .map(ParsedFilter.class::cast)
       .flatMap(projectBucket -> ((ParsedStringTerms) projectBucket.getAggregations().get("branchUuid")).getBuckets().stream()
         .flatMap(branchBucket -> {
           long count = ((ParsedValueCount) branchBucket.getAggregations().get(AGG_COUNT)).getValue();
index 989936ff93b7351a986e9e94dc802faae8023581..ef43b30c23a34956999be70a82134f137689477e 100644 (file)
@@ -133,6 +133,8 @@ public class IssueQueryFactory {
         .languages(request.getLanguages())
         .tags(request.getTags())
         .types(request.getTypes())
+        .pciDss32(request.getPciDss32())
+        .pciDss40(request.getPciDss40())
         .owaspTop10(request.getOwaspTop10())
         .owaspTop10For2021(request.getOwaspTop10For2021())
         .sansTop25(request.getSansTop25())
@@ -199,8 +201,8 @@ public class IssueQueryFactory {
       if (!QUALIFIERS_WITHOUT_LEAK_PERIOD.contains(component.qualifier()) && request.getPullRequest() == null) {
         Optional<SnapshotDto> snapshot = getLastAnalysis(dbSession, component);
         if (!snapshot.isEmpty() && isLastAnalysisFromReAnalyzedReferenceBranch(dbSession, snapshot.get())) {
-            builder.newCodeOnReference(true);
-            return;
+          builder.newCodeOnReference(true);
+          return;
         }
         // if last analysis has no period date, then no issue should be considered new.
         Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(snapshot);
index 0a595df01e94185d9194cba0268c440f7fdb4525..94225742656bccc8627bfa58378c36493962b02a 100644 (file)
 package org.sonar.server.issue.index;
 
 import java.util.Map;
-import java.util.TimeZone;
-import org.junit.Rule;
 import org.junit.Test;
-import org.sonar.api.impl.utils.TestSystem2;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.rule.Severity;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentTesting;
-import org.sonar.server.es.EsTester;
 import org.sonar.server.es.Facets;
 import org.sonar.server.es.SearchOptions;
 import org.sonar.server.issue.IssueDocTesting;
 import org.sonar.server.issue.index.IssueQuery.Builder;
-import org.sonar.server.permission.index.IndexPermissions;
-import org.sonar.server.permission.index.PermissionIndexerTester;
-import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
-import org.sonar.server.tester.UserSessionRule;
 
-import static java.util.Arrays.asList;
-import static java.util.Arrays.stream;
 import static java.util.Collections.singletonList;
-import static java.util.stream.Collectors.toList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.entry;
-import static org.mockito.Mockito.mock;
 import static org.sonar.api.issue.Issue.STATUS_CLOSED;
 import static org.sonar.api.issue.Issue.STATUS_OPEN;
-import static org.sonar.api.resources.Qualifiers.PROJECT;
 import static org.sonar.api.utils.DateUtils.parseDateTime;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT;
 
-public class IssueIndexDebtTest {
-
-  private final System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(TimeZone.getTimeZone("GMT-01:00"));
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public UserSessionRule userSessionRule = UserSessionRule.standalone();
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  private final AsyncIssueIndexing asyncIssueIndexing = mock(AsyncIssueIndexing.class);
-  private final IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), asyncIssueIndexing);
-  private final PermissionIndexerTester authorizationIndexer = new PermissionIndexerTester(es, issueIndexer);
-  private final IssueIndex underTest = new IssueIndex(es.client(), system2, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
+public class IssueIndexDebtTest extends IssueIndexTestCommon {
 
   @Test
   public void facets_on_projects() {
@@ -248,11 +219,6 @@ public class IssueIndexDebtTest {
     return new SearchOptions().addFacets("createdAt");
   }
 
-  private void indexIssues(IssueDoc... issues) {
-    issueIndexer.index(asList(issues).iterator());
-    authorizationIndexer.allow(stream(issues).map(issue -> new IndexPermissions(issue.projectUuid(), PROJECT).allowAnyone()).collect(toList()));
-  }
-
   private Facets search(String additionalFacet) {
     return new Facets(underTest.search(newQueryBuilder().build(), new SearchOptions().addFacets(singletonList(additionalFacet))), system2.getDefaultTimeZone().toZoneId());
   }
index ac1062dde0a99d750f23f530c4c3a8473fef05c4..20d86b48c4053bd7e4afdf0fc73293403c4616dc 100644 (file)
@@ -23,30 +23,17 @@ import java.time.ZoneId;
 import java.util.Collections;
 import java.util.Date;
 import java.util.Map;
-import java.util.TimeZone;
 import org.elasticsearch.action.search.SearchResponse;
-import org.junit.Rule;
 import org.junit.Test;
-import org.sonar.api.impl.utils.TestSystem2;
 import org.sonar.api.rules.RuleType;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.rule.RuleDto;
-import org.sonar.server.es.EsTester;
 import org.sonar.server.es.Facets;
 import org.sonar.server.es.SearchOptions;
-import org.sonar.server.permission.index.IndexPermissions;
-import org.sonar.server.permission.index.PermissionIndexerTester;
-import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
 import org.sonar.server.security.SecurityStandards.SQCategory;
-import org.sonar.server.tester.UserSessionRule;
 
 import static java.util.Arrays.asList;
-import static java.util.Arrays.stream;
 import static java.util.Collections.singletonList;
-import static java.util.TimeZone.getTimeZone;
-import static java.util.stream.Collectors.toList;
 import static java.util.stream.IntStream.rangeClosed;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.entry;
@@ -59,7 +46,6 @@ import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
 import static org.sonar.api.issue.Issue.STATUS_OPEN;
 import static org.sonar.api.issue.Issue.STATUS_REOPENED;
 import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
-import static org.sonar.api.resources.Qualifiers.PROJECT;
 import static org.sonar.api.rule.Severity.BLOCKER;
 import static org.sonar.api.rule.Severity.CRITICAL;
 import static org.sonar.api.rule.Severity.INFO;
@@ -67,6 +53,8 @@ import static org.sonar.api.rule.Severity.MAJOR;
 import static org.sonar.api.rule.Severity.MINOR;
 import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2017;
 import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2021;
+import static org.sonar.api.server.rule.RulesDefinition.PciDssVersion.V3_2;
+import static org.sonar.api.server.rule.RulesDefinition.PciDssVersion.V4_0;
 import static org.sonar.api.utils.DateUtils.parseDateTime;
 import static org.sonar.db.component.ComponentTesting.newDirectory;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
@@ -74,21 +62,7 @@ import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
 import static org.sonar.db.rule.RuleTesting.newRule;
 import static org.sonar.server.issue.IssueDocTesting.newDoc;
 
-public class IssueIndexFacetsTest {
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public UserSessionRule userSessionRule = UserSessionRule.standalone();
-  private final TimeZone defaultTimezone = getTimeZone("GMT-01:00");
-  private System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(defaultTimezone);
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), null);
-  private PermissionIndexerTester authorizationIndexer = new PermissionIndexerTester(es, issueIndexer);
-
-  private IssueIndex underTest = new IssueIndex(es.client(), system2, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
+public class IssueIndexFacetsTest extends IssueIndexTestCommon {
 
   @Test
   public void facet_on_projectUuids() {
@@ -187,6 +161,38 @@ public class IssueIndexFacetsTest {
       entry("89", 1L));
   }
 
+  @Test
+  public void facets_on_pciDss32() {
+    ComponentDto project = newPrivateProjectDto();
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setType(RuleType.VULNERABILITY).setPciDss32(asList("1", "2")),
+      newDoc("I2", file).setType(RuleType.VULNERABILITY).setPciDss32(singletonList("3")),
+      newDoc("I3", file));
+
+    assertThatFacetHasOnly(IssueQuery.builder(), V3_2.prefix(),
+      entry("1", 1L),
+      entry("2", 1L),
+      entry("3", 1L));
+  }
+
+  @Test
+  public void facets_on_pciDss40() {
+    ComponentDto project = newPrivateProjectDto();
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setType(RuleType.VULNERABILITY).setPciDss40(asList("1", "2")),
+      newDoc("I2", file).setType(RuleType.VULNERABILITY).setPciDss40(singletonList("3")),
+      newDoc("I3", file));
+
+    assertThatFacetHasOnly(IssueQuery.builder(), V4_0.prefix(),
+      entry("1", 1L),
+      entry("2", 1L),
+      entry("3", 1L));
+  }
+
   @Test
   public void facets_on_owaspTop10() {
     ComponentDto project = newPrivateProjectDto();
@@ -637,11 +643,6 @@ public class IssueIndexFacetsTest {
     return new SearchOptions().addFacets("createdAt");
   }
 
-  private void indexIssues(IssueDoc... issues) {
-    issueIndexer.index(asList(issues).iterator());
-    authorizationIndexer.allow(stream(issues).map(issue -> new IndexPermissions(issue.projectUuid(), PROJECT).allowAnyone()).collect(toList()));
-  }
-
   @SafeVarargs
   private final void assertThatFacetHasExactly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) {
     SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet)));
index a41d651ba9ab0672f12c6cb4dc7560d0dcb9de15..8312ff90cf4d6932261c35022ef3c023b47bf54e 100644 (file)
 package org.sonar.server.issue.index;
 
 import com.google.common.collect.ImmutableMap;
-import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 import java.util.Set;
-import java.util.stream.Collectors;
 import org.assertj.core.api.Fail;
-import org.elasticsearch.search.SearchHit;
-import org.junit.Rule;
 import org.junit.Test;
-import org.sonar.api.impl.utils.TestSystem2;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.rules.RuleType;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.rule.RuleDto;
-import org.sonar.server.es.EsTester;
 import org.sonar.server.es.SearchOptions;
-import org.sonar.server.permission.index.IndexPermissions;
-import org.sonar.server.permission.index.PermissionIndexerTester;
-import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
 import org.sonar.server.security.SecurityStandards.SQCategory;
-import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.view.index.ViewDoc;
-import org.sonar.server.view.index.ViewIndexer;
 
 import static java.util.Arrays.asList;
-import static java.util.Arrays.stream;
 import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
-import static java.util.TimeZone.getTimeZone;
-import static java.util.stream.Collectors.toList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.sonar.api.resources.Qualifiers.APP;
-import static org.sonar.api.resources.Qualifiers.PROJECT;
 import static org.sonar.api.utils.DateUtils.addDays;
 import static org.sonar.api.utils.DateUtils.parseDate;
 import static org.sonar.api.utils.DateUtils.parseDateTime;
@@ -66,20 +49,7 @@ import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
 import static org.sonar.db.rule.RuleTesting.newRule;
 import static org.sonar.server.issue.IssueDocTesting.newDoc;
 
-public class IssueIndexFiltersTest {
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public UserSessionRule userSessionRule = UserSessionRule.standalone();
-  private final System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(getTimeZone("GMT-01:00"));
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  private final IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), null);
-  private final ViewIndexer viewIndexer = new ViewIndexer(db.getDbClient(), es.client());
-  private final PermissionIndexerTester authorizationIndexer = new PermissionIndexerTester(es, issueIndexer);
-  private final IssueIndex underTest = new IssueIndex(es.client(), system2, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
+public class IssueIndexFiltersTest extends IssueIndexTestCommon {
 
   @Test
   public void filter_by_keys() {
@@ -844,31 +814,7 @@ public class IssueIndexFiltersTest {
     assertThatSearchReturnsOnly(IssueQuery.builder().sonarsourceSecurity(singletonList("buffer-overflow")), "I1");
   }
 
-  private void indexIssues(IssueDoc... issues) {
-    issueIndexer.index(asList(issues).iterator());
-    authorizationIndexer.allow(stream(issues).map(issue -> new IndexPermissions(issue.projectUuid(), PROJECT).allowAnyone()).collect(toList()));
-  }
-
   private void indexView(String viewUuid, List<String> projects) {
     viewIndexer.index(new ViewDoc().setUuid(viewUuid).setProjects(projects));
   }
-
-  /**
-   * Execute the search request and return the document ids of results.
-   */
-  private List<String> searchAndReturnKeys(IssueQuery.Builder query) {
-    return Arrays.stream(underTest.search(query.build(), new SearchOptions()).getHits().getHits())
-      .map(SearchHit::getId)
-      .collect(Collectors.toList());
-  }
-
-  private void assertThatSearchReturnsOnly(IssueQuery.Builder query, String... expectedIssueKeys) {
-    List<String> keys = searchAndReturnKeys(query);
-    assertThat(keys).containsExactlyInAnyOrder(expectedIssueKeys);
-  }
-
-  private void assertThatSearchReturnsEmpty(IssueQuery.Builder query) {
-    List<String> keys = searchAndReturnKeys(query);
-    assertThat(keys).isEmpty();
-  }
 }
index 072793388652c2aa7e061897ac754717e10bb148..59739bf78be2a11bab4aa9847fcc0b6180a24224 100644 (file)
@@ -21,44 +21,22 @@ package org.sonar.server.issue.index;
 
 import java.util.Date;
 import java.util.List;
-import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.issue.Issue;
-import org.sonar.api.utils.System2;
 import org.sonar.db.component.ComponentDto;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.permission.index.IndexPermissions;
-import org.sonar.server.permission.index.PermissionIndexerTester;
-import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
-import org.sonar.server.tester.UserSessionRule;
 
 import static java.util.Arrays.asList;
-import static java.util.Arrays.stream;
 import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
-import static java.util.stream.Collectors.toList;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
-import static org.mockito.Mockito.mock;
-import static org.sonar.api.resources.Qualifiers.PROJECT;
+import static org.sonar.db.component.ComponentTesting.newBranchComponent;
 import static org.sonar.db.component.ComponentTesting.newBranchDto;
 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
-import static org.sonar.db.component.ComponentTesting.newBranchComponent;
 import static org.sonar.server.issue.IssueDocTesting.newDoc;
 
-public class IssueIndexProjectStatisticsTest {
-
-  private System2 system2 = mock(System2.class);
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public UserSessionRule userSessionRule = UserSessionRule.standalone();
-
-  private IssueIndexer issueIndexer = new IssueIndexer(es.client(), null, new IssueIteratorFactory(null), null);
-  private PermissionIndexerTester authorizationIndexer = new PermissionIndexerTester(es, issueIndexer);
-
-  private IssueIndex underTest = new IssueIndex(es.client(), system2, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
+public class IssueIndexProjectStatisticsTest extends IssueIndexTestCommon {
 
   @Test
   public void searchProjectStatistics_returns_empty_list_if_no_input() {
@@ -249,9 +227,4 @@ public class IssueIndexProjectStatisticsTest {
         tuple(2L, branch.uuid(), from + 2L),
         tuple(1L, project.uuid(), from + 1L));
   }
-
-  private void indexIssues(IssueDoc... issues) {
-    issueIndexer.index(asList(issues).iterator());
-    authorizationIndexer.allow(stream(issues).map(issue -> new IndexPermissions(issue.projectUuid(), PROJECT).allowAnyone()).collect(toList()));
-  }
 }
diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityCategoriesTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityCategoriesTest.java
new file mode 100644 (file)
index 0000000..4477c76
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.index;
+
+import java.util.List;
+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.db.component.ComponentDto;
+
+import static java.util.Arrays.asList;
+import static java.util.Arrays.stream;
+import static java.util.stream.Collectors.toList;
+import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
+import static org.sonar.server.issue.IssueDocTesting.newDoc;
+
+public class IssueIndexSecurityCategoriesTest extends IssueIndexTestCommon {
+
+  @Test
+  public void searchSinglePciDss32Category() {
+    ComponentDto project = newPrivateProjectDto();
+
+    indexIssues(
+      newDoc("openvul1", project).setPciDss32(asList("1.2.0", "3.4.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+        .setSeverity(Severity.MAJOR),
+      newDoc("openvul2", project).setPciDss32(asList("3.3.2", "1.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+        .setSeverity(Severity.MINOR)
+    );
+
+    assertThatSearchReturnsOnly(queryPciDss32("1"), "openvul1", "openvul2");
+    assertThatSearchReturnsOnly(queryPciDss32("1.2.0"), "openvul1");
+    assertThatSearchReturnsEmpty(queryPciDss32("1.2"));
+  }
+
+  @Test
+  public void searchMultiplePciDss32Categories() {
+    ComponentDto project = newPrivateProjectDto();
+
+    indexIssues(
+      newDoc("openvul1", project).setPciDss32(asList("1.2.0", "3.4.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+        .setSeverity(Severity.MAJOR),
+      newDoc("openvul2", project).setPciDss32(asList("3.3.2", "2.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+        .setSeverity(Severity.MINOR),
+      newDoc("openvul3", project).setPciDss32(asList("4.1", "5.4")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+        .setSeverity(Severity.MINOR)
+    );
+
+    assertThatSearchReturnsOnly(queryPciDss32("1", "4"), "openvul1", "openvul3");
+    assertThatSearchReturnsOnly(queryPciDss32("1.2.0", "5.4"), "openvul1", "openvul3");
+    assertThatSearchReturnsEmpty(queryPciDss32("6", "7", "8", "9", "10", "11", "12"));
+  }
+
+  @Test
+  public void searchSinglePciDss40Category() {
+    ComponentDto project = newPrivateProjectDto();
+
+    indexIssues(
+      newDoc("openvul1", project).setPciDss40(asList("1.2.0", "3.4.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+        .setSeverity(Severity.MAJOR),
+      newDoc("openvul2", project).setPciDss40(asList("3.3.2", "1.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+        .setSeverity(Severity.MINOR)
+    );
+
+    assertThatSearchReturnsOnly(queryPciDss40("1"), "openvul1", "openvul2");
+    assertThatSearchReturnsOnly(queryPciDss40("1.2.0"), "openvul1");
+    assertThatSearchReturnsEmpty(queryPciDss40("1.2"));
+  }
+
+  @Test
+  public void searchMultiplePciDss40Categories() {
+    ComponentDto project = newPrivateProjectDto();
+
+    indexIssues(
+      newDoc("openvul1", project).setPciDss40(asList("1.2.0", "3.4.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+        .setSeverity(Severity.MAJOR),
+      newDoc("openvul2", project).setPciDss40(asList("3.3.2", "2.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+        .setSeverity(Severity.MINOR),
+      newDoc("openvul3", project).setPciDss40(asList("4.1", "5.4")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+        .setSeverity(Severity.MINOR)
+    );
+
+    assertThatSearchReturnsOnly(queryPciDss40("1", "4"), "openvul1", "openvul3");
+    assertThatSearchReturnsOnly(queryPciDss40("1.2.0", "5.4"), "openvul1", "openvul3");
+    assertThatSearchReturnsEmpty(queryPciDss40("6", "7", "8", "9", "10", "11", "12"));
+  }
+
+  @Test
+  public void searchMixedPciDssCategories() {
+    ComponentDto project = newPrivateProjectDto();
+
+    indexIssues(
+      newDoc("openvul1", project).setPciDss40(asList("1.2.0", "3.4.5")).setPciDss32(List.of("2.1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+        .setSeverity(Severity.MAJOR),
+      newDoc("openvul2", project).setPciDss40(asList("3.3.2", "2.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+        .setSeverity(Severity.MINOR),
+      newDoc("openvul3", project).setPciDss32(asList("4.1", "5.4")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+        .setSeverity(Severity.MINOR)
+    );
+
+    assertThatSearchReturnsOnly(queryPciDss40("1", "4"), "openvul1");
+    assertThatSearchReturnsOnly(queryPciDss40("1.2.0", "5.4"), "openvul1");
+    assertThatSearchReturnsEmpty(queryPciDss40("6", "7", "8", "9", "10", "11", "12"));
+
+    assertThatSearchReturnsOnly(queryPciDss32("3", "2.1"), "openvul1");
+    assertThatSearchReturnsOnly(queryPciDss32("1", "2"), "openvul1");
+    assertThatSearchReturnsOnly(queryPciDss32("4", "3"), "openvul3");
+    assertThatSearchReturnsEmpty(queryPciDss32("1", "3", "6", "7", "8", "9", "10", "11", "12"));
+
+  }
+
+  private IssueQuery.Builder queryPciDss32(String... values) {
+    return IssueQuery.builder()
+      .pciDss32(stream(values).collect(toList()))
+      .types(List.of("CODE_SMELL", "BUG", "VULNERABILITY"));
+  }
+
+  private IssueQuery.Builder queryPciDss40(String... values) {
+    return IssueQuery.builder()
+      .pciDss40(stream(values).collect(toList()))
+      .types(List.of("CODE_SMELL", "BUG", "VULNERABILITY"));
+  }
+}
index ee9d8d94c8044b2644b8a7b2a740807ae5929c8c..951397edc6dd0db570873f1202576ff98a5e2e52 100644 (file)
  */
 package org.sonar.server.issue.index;
 
-import java.util.Arrays;
-import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
 import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.search.SearchHit;
-import org.junit.Rule;
 import org.junit.Test;
-import org.sonar.api.impl.utils.TestSystem2;
 import org.sonar.api.rule.Severity;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDto;
-import org.sonar.server.es.EsTester;
 import org.sonar.server.es.Facets;
 import org.sonar.server.es.SearchOptions;
 import org.sonar.server.permission.index.IndexPermissions;
-import org.sonar.server.permission.index.PermissionIndexerTester;
-import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
-import org.sonar.server.tester.UserSessionRule;
 
 import static java.util.Arrays.asList;
 import static java.util.Arrays.stream;
 import static java.util.Collections.singletonList;
-import static java.util.TimeZone.getTimeZone;
 import static java.util.stream.Collectors.toList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.entry;
@@ -58,20 +45,7 @@ import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
 import static org.sonar.server.issue.IssueDocTesting.newDoc;
 
-public class IssueIndexSecurityHotspotsTest {
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public UserSessionRule userSessionRule = UserSessionRule.standalone();
-  private System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(getTimeZone("GMT-01:00"));
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), null);
-  private PermissionIndexerTester authorizationIndexer = new PermissionIndexerTester(es, issueIndexer);
-
-  private IssueIndex underTest = new IssueIndex(es.client(), system2, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
+public class IssueIndexSecurityHotspotsTest extends IssueIndexTestCommon {
 
   @Test
   public void filter_by_security_hotspots_type() {
@@ -129,11 +103,6 @@ public class IssueIndexSecurityHotspotsTest {
     assertThatFacetHasOnly(IssueQuery.builder().types(singletonList(SECURITY_HOTSPOT.name())), "severities");
   }
 
-  private void indexIssues(IssueDoc... issues) {
-    issueIndexer.index(asList(issues).iterator());
-    authorizationIndexer.allow(stream(issues).map(issue -> new IndexPermissions(issue.projectUuid(), PROJECT).allowAnyone()).collect(toList()));
-  }
-
   @SafeVarargs
   private final void assertThatFacetHasOnly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) {
     SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet)));
@@ -142,20 +111,4 @@ public class IssueIndexSecurityHotspotsTest {
     assertThat(facets.get(facet)).containsOnly(expectedEntries);
   }
 
-  private void assertThatSearchReturnsOnly(IssueQuery.Builder query, String... expectedIssueKeys) {
-    List<String> keys = searchAndReturnKeys(query);
-    assertThat(keys).containsExactlyInAnyOrder(expectedIssueKeys);
-  }
-
-  private void assertThatSearchReturnsEmpty(IssueQuery.Builder query) {
-    List<String> keys = searchAndReturnKeys(query);
-    assertThat(keys).isEmpty();
-  }
-
-  private List<String> searchAndReturnKeys(IssueQuery.Builder query) {
-    return Arrays.stream(underTest.search(query.build(), new SearchOptions()).getHits().getHits())
-      .map(SearchHit::getId)
-      .collect(Collectors.toList());
-  }
-
 }
index 82bf992f1b8f21006bde04ef1ab5699ad0d6d43c..8a7952c6e1c165388f7ca5105eb5698f6b826699 100644 (file)
@@ -23,34 +23,21 @@ import java.util.List;
 import java.util.Map;
 import java.util.OptionalInt;
 import java.util.stream.Collectors;
-import org.junit.Rule;
 import org.junit.Test;
-import org.sonar.api.impl.utils.TestSystem2;
 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.api.utils.System2;
-import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDto;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.permission.index.IndexPermissions;
-import org.sonar.server.permission.index.PermissionIndexerTester;
-import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
-import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.view.index.ViewDoc;
-import org.sonar.server.view.index.ViewIndexer;
 
 import static java.lang.Integer.parseInt;
 import static java.util.Arrays.asList;
-import static java.util.Arrays.stream;
 import static java.util.Collections.singletonList;
 import static java.util.Comparator.comparing;
-import static java.util.TimeZone.getTimeZone;
 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.resources.Qualifiers.PROJECT;
 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;
@@ -60,21 +47,7 @@ import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_POROUS_DEF
 import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_RISKY_RESOURCE;
 import static org.sonar.server.security.SecurityStandards.UNKNOWN_STANDARD;
 
-public class IssueIndexSecurityReportsTest {
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public UserSessionRule userSessionRule = UserSessionRule.standalone();
-  private System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(getTimeZone("GMT-01:00"));
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), null);
-  private ViewIndexer viewIndexer = new ViewIndexer(db.getDbClient(), es.client());
-  private PermissionIndexerTester authorizationIndexer = new PermissionIndexerTester(es, issueIndexer);
-
-  private IssueIndex underTest = new IssueIndex(es.client(), system2, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
+public class IssueIndexSecurityReportsTest extends IssueIndexTestCommon {
 
   @Test
   public void getOwaspTop10Report_dont_count_vulnerabilities_from_other_projects() {
@@ -675,11 +648,6 @@ public class IssueIndexSecurityReportsTest {
     return statistics.getChildren().stream().filter(stat -> stat.getCategory().equals(cweId)).findAny().orElse(null);
   }
 
-  private void indexIssues(IssueDoc... issues) {
-    issueIndexer.index(asList(issues).iterator());
-    authorizationIndexer.allow(stream(issues).map(issue -> new IndexPermissions(issue.projectUuid(), PROJECT).allowAnyone()).collect(toList()));
-  }
-
   private void indexView(String viewUuid, List<String> projects) {
     viewIndexer.index(new ViewDoc().setUuid(viewUuid).setProjects(projects));
   }
index 200a0010f3e1bda9967f8246e04d399bf0370f1a..3fb1906468bd8a135c2ba88e2235ad49d45e1147 100644 (file)
  */
 package org.sonar.server.issue.index;
 
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
 import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.search.SearchHit;
-import org.junit.Rule;
 import org.junit.Test;
-import org.sonar.api.impl.utils.TestSystem2;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.rule.Severity;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDto;
-import org.sonar.server.es.EsTester;
 import org.sonar.server.es.SearchOptions;
-import org.sonar.server.permission.index.IndexPermissions;
-import org.sonar.server.permission.index.PermissionIndexerTester;
-import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
-import org.sonar.server.tester.UserSessionRule;
-
-import static java.util.Arrays.asList;
-import static java.util.Arrays.stream;
-import static java.util.TimeZone.getTimeZone;
-import static java.util.stream.Collectors.toList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.sonar.api.resources.Qualifiers.PROJECT;
+
 import static org.sonar.api.utils.DateUtils.parseDateTime;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
 import static org.sonar.server.issue.IssueDocTesting.newDoc;
 
-public class IssueIndexSortTest {
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public UserSessionRule userSessionRule = UserSessionRule.standalone();
-  private final System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(getTimeZone("GMT-01:00"));
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  private final AsyncIssueIndexing asyncIssueIndexing = mock(AsyncIssueIndexing.class);
-  private final IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), asyncIssueIndexing);
-  private final PermissionIndexerTester authorizationIndexer = new PermissionIndexerTester(es, issueIndexer);
-  private final IssueIndex underTest = new IssueIndex(es.client(), system2, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
+public class IssueIndexSortTest extends IssueIndexTestCommon {
 
   @Test
   public void sort_by_status() {
@@ -209,23 +176,4 @@ public class IssueIndexSortTest {
     assertThatSearchReturnsOnly(IssueQuery.builder(), "F3_1", "F1_2", "F1_1", "F1_3", "F2_1", "F2_2", "F2_3", "F3_2");
   }
 
-  private void indexIssues(IssueDoc... issues) {
-    issueIndexer.index(asList(issues).iterator());
-    authorizationIndexer.allow(stream(issues).map(issue -> new IndexPermissions(issue.projectUuid(), PROJECT).allowAnyone()).collect(toList()));
-  }
-
-  /**
-   * Execute the search request and return the document ids of results.
-   */
-  private List<String> searchAndReturnKeys(IssueQuery.Builder query) {
-    return Arrays.stream(underTest.search(query.build(), new SearchOptions()).getHits().getHits())
-      .map(SearchHit::getId)
-      .collect(Collectors.toList());
-  }
-
-  private void assertThatSearchReturnsOnly(IssueQuery.Builder query, String... expectedIssueKeys) {
-    List<String> keys = searchAndReturnKeys(query);
-    assertThat(keys).containsExactlyInAnyOrder(expectedIssueKeys);
-  }
-
 }
index 3b887d767048aaabdf013c9feea7fee6356fb2b5..e62a4b6352862999eda43dc9806e72dabbe0735f 100644 (file)
@@ -22,46 +22,29 @@ package org.sonar.server.issue.index;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterators;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
-import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import org.apache.lucene.search.TotalHits;
 import org.apache.lucene.search.TotalHits.Relation;
 import org.assertj.core.groups.Tuple;
 import org.elasticsearch.action.search.SearchResponse;
 import org.elasticsearch.search.SearchHit;
-import org.junit.Rule;
 import org.junit.Test;
-import org.sonar.api.impl.utils.TestSystem2;
 import org.sonar.api.issue.Issue;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.user.GroupDto;
 import org.sonar.db.user.UserDto;
-import org.sonar.server.es.EsTester;
 import org.sonar.server.es.SearchOptions;
-import org.sonar.server.permission.index.IndexPermissions;
-import org.sonar.server.permission.index.PermissionIndexerTester;
-import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
-import org.sonar.server.rule.index.RuleIndexer;
-import org.sonar.server.tester.UserSessionRule;
 
 import static com.google.common.collect.ImmutableSortedSet.of;
 import static java.util.Arrays.asList;
-import static java.util.Arrays.stream;
 import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
-import static java.util.TimeZone.getTimeZone;
-import static java.util.stream.Collectors.toList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.entry;
 import static org.assertj.core.api.Assertions.tuple;
-import static org.mockito.Mockito.mock;
 import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
-import static org.sonar.api.resources.Qualifiers.PROJECT;
 import static org.sonar.api.rules.RuleType.BUG;
 import static org.sonar.api.rules.RuleType.CODE_SMELL;
 import static org.sonar.api.rules.RuleType.VULNERABILITY;
@@ -71,22 +54,7 @@ import static org.sonar.db.user.GroupTesting.newGroupDto;
 import static org.sonar.db.user.UserTesting.newUserDto;
 import static org.sonar.server.issue.IssueDocTesting.newDoc;
 
-public class IssueIndexTest {
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public UserSessionRule userSessionRule = UserSessionRule.standalone();
-  private final System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(getTimeZone("GMT-01:00"));
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  private final AsyncIssueIndexing asyncIssueIndexing = mock(AsyncIssueIndexing.class);
-  private final IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), asyncIssueIndexing);
-  private final RuleIndexer ruleIndexer = new RuleIndexer(es.client(), db.getDbClient());
-  private final PermissionIndexerTester authorizationIndexer = new PermissionIndexerTester(es, issueIndexer);
-
-  private final IssueIndex underTest = new IssueIndex(es.client(), system2, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
+public class IssueIndexTest extends IssueIndexTestCommon {
 
   @Test
   public void paging() {
@@ -387,32 +355,7 @@ public class IssueIndexTest {
     return IssueQuery.builder().projectUuids(singletonList(projectUuid)).resolved(false).build();
   }
 
-  private void indexIssues(IssueDoc... issues) {
-    issueIndexer.index(asList(issues).iterator());
-    authorizationIndexer.allow(stream(issues).map(issue -> new IndexPermissions(issue.projectUuid(), PROJECT).allowAnyone()).collect(toList()));
-  }
-
   private void indexIssue(IssueDoc issue) {
     issueIndexer.index(Iterators.singletonIterator(issue));
   }
-
-  /**
-   * Execute the search request and return the document ids of results.
-   */
-  private List<String> searchAndReturnKeys(IssueQuery.Builder query) {
-    return Arrays.stream(underTest.search(query.build(), new SearchOptions()).getHits().getHits())
-      .map(SearchHit::getId)
-      .collect(Collectors.toList());
-  }
-
-  private void assertThatSearchReturnsOnly(IssueQuery.Builder query, String... expectedIssueKeys) {
-    List<String> keys = searchAndReturnKeys(query);
-    assertThat(keys).containsExactlyInAnyOrder(expectedIssueKeys);
-  }
-
-  private void assertThatSearchReturnsEmpty(IssueQuery.Builder query) {
-    List<String> keys = searchAndReturnKeys(query);
-    assertThat(keys).isEmpty();
-  }
-
 }
diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexTestCommon.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexTestCommon.java
new file mode 100644 (file)
index 0000000..ca097cc
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.index;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.elasticsearch.search.SearchHit;
+import org.junit.Rule;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.SearchOptions;
+import org.sonar.server.permission.index.IndexPermissions;
+import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
+import org.sonar.server.rule.index.RuleIndexer;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.view.index.ViewIndexer;
+
+import static java.util.Arrays.asList;
+import static java.util.Arrays.stream;
+import static java.util.TimeZone.getTimeZone;
+import static java.util.stream.Collectors.toList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.sonar.api.resources.Qualifiers.PROJECT;
+
+public class IssueIndexTestCommon {
+
+  @Rule
+  public EsTester es = EsTester.create();
+  @Rule
+  public UserSessionRule userSessionRule = UserSessionRule.standalone();
+  protected final System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(getTimeZone("GMT-01:00"));
+  @Rule
+  public DbTester db = DbTester.create(system2);
+
+  private final AsyncIssueIndexing asyncIssueIndexing = mock(AsyncIssueIndexing.class);
+  protected final IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), asyncIssueIndexing);
+  protected final RuleIndexer ruleIndexer = new RuleIndexer(es.client(), db.getDbClient());
+  protected final PermissionIndexerTester authorizationIndexer = new PermissionIndexerTester(es, issueIndexer);
+  protected final ViewIndexer viewIndexer = new ViewIndexer(db.getDbClient(), es.client());
+
+  protected final IssueIndex underTest = new IssueIndex(es.client(), system2, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
+
+  /**
+   * Execute the search request and return the document ids of results.
+   */
+  protected List<String> searchAndReturnKeys(IssueQuery.Builder query) {
+    return Arrays.stream(underTest.search(query.build(), new SearchOptions()).getHits().getHits())
+      .map(SearchHit::getId)
+      .collect(Collectors.toList());
+  }
+
+  protected void assertThatSearchReturnsOnly(IssueQuery.Builder query, String... expectedIssueKeys) {
+    List<String> keys = searchAndReturnKeys(query);
+    assertThat(keys).containsExactlyInAnyOrder(expectedIssueKeys);
+  }
+
+  protected void assertThatSearchReturnsEmpty(IssueQuery.Builder query) {
+    List<String> keys = searchAndReturnKeys(query);
+    assertThat(keys).isEmpty();
+  }
+
+  protected void indexIssues(IssueDoc... issues) {
+    issueIndexer.index(asList(issues).iterator());
+    authorizationIndexer.allow(stream(issues).map(issue -> new IndexPermissions(issue.projectUuid(), PROJECT).allowAnyone()).collect(toList()));
+  }
+
+}
index 462affd308742833bae382115748a3d4fff619d1..9a825efd39ab691d0f27684a839eec4a722637a1 100644 (file)
@@ -112,6 +112,8 @@ 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_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;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PCI_DSS_40;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECTS;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PULL_REQUEST;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLUTIONS;
@@ -147,6 +149,8 @@ public class SearchAction implements IssuesWsAction {
     PARAM_LANGUAGES,
     PARAM_TAGS,
     PARAM_TYPES,
+    PARAM_PCI_DSS_32,
+    PARAM_PCI_DSS_40,
     PARAM_OWASP_TOP_10,
     PARAM_OWASP_TOP_10_2021,
     PARAM_SANS_TOP_25,
@@ -189,6 +193,8 @@ public class SearchAction implements IssuesWsAction {
         + "<br/>When issue indexation is in progress returns 503 service unavailable HTTP code.")
       .setSince("3.6")
       .setChangelog(
+        new Change("9.6", "Added facets 'pciDss-3.2' and 'pciDss-4.0"),
+        new Change("9.6", "Added parameters 'pciDss-3.2' and 'pciDss-4.0"),
         new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
         new Change("9.6", "New possible value for 'additionalFields' parameter: 'ruleDescriptionContextKey'"),
         new Change("9.6", "Facet 'moduleUuids' is dropped."),
@@ -261,6 +267,14 @@ public class SearchAction implements IssuesWsAction {
       .setSince("5.5")
       .setPossibleValues(ALL_RULE_TYPES_EXCEPT_SECURITY_HOTSPOTS)
       .setExampleValue(format("%s,%s", RuleType.CODE_SMELL, RuleType.BUG));
+    action.createParam(PARAM_PCI_DSS_32)
+      .setDescription("Comma-separated list of PCI DSS v3.2 categories.")
+      .setSince("9.6")
+      .setExampleValue("4,6.5.8,10.1");
+    action.createParam(PARAM_PCI_DSS_40)
+      .setDescription("Comma-separated list of PCI DSS v4.0 categories.")
+      .setSince("9.6")
+      .setExampleValue("4,6.5.8,10.1");
     action.createParam(PARAM_OWASP_TOP_10)
       .setDescription("Comma-separated list of OWASP Top 10 2017 lowercase categories.")
       .setSince("7.3")
@@ -466,6 +480,8 @@ public class SearchAction implements IssuesWsAction {
 
     setTypesFacet(facets);
 
+    addMandatoryValuesToFacet(facets, PARAM_PCI_DSS_32, request.getPciDss32());
+    addMandatoryValuesToFacet(facets, PARAM_PCI_DSS_40, request.getPciDss40());
     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());
@@ -485,9 +501,7 @@ public class SearchAction implements IssuesWsAction {
     Map<String, Long> buckets = facets.get(facetName);
     if (buckets != null && mandatoryValues != null) {
       for (String mandatoryValue : mandatoryValues) {
-        if (!buckets.containsKey(mandatoryValue)) {
-          buckets.put(mandatoryValue, 0L);
-        }
+        buckets.putIfAbsent(mandatoryValue, 0L);
       }
     }
   }
@@ -542,6 +556,8 @@ public class SearchAction implements IssuesWsAction {
       .setStatuses(request.paramAsStrings(PARAM_STATUSES))
       .setTags(request.paramAsStrings(PARAM_TAGS))
       .setTypes(allRuleTypesExceptHotspotsIfEmpty(request.paramAsStrings(PARAM_TYPES)))
+      .setPciDss32(request.paramAsStrings(PARAM_PCI_DSS_32))
+      .setPciDss40(request.paramAsStrings(PARAM_PCI_DSS_40))
       .setOwaspTop10(request.paramAsStrings(PARAM_OWASP_TOP_10))
       .setOwaspTop10For2021(request.paramAsStrings(PARAM_OWASP_TOP_10_2021))
       .setSansTop25(request.paramAsStrings(PARAM_SANS_TOP_25))
index 0d1d86a81e2416a7411838e5ac82ab3006b585d0..73573a7d07440b5f4cb7ba80fbcc5f07eb4fccee 100644 (file)
@@ -214,8 +214,7 @@ public class SearchActionTest {
         .getIssuesList()
         .get(0)
         .getActions()
-        .getActionsList()
-    ).isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY, ACTION_ASSIGN));
+        .getActionsList()).isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY, ACTION_ASSIGN));
 
     response = ws.newRequest()
       .setParam(PARAM_ADDITIONAL_FIELDS, "actions")
@@ -227,8 +226,7 @@ public class SearchActionTest {
         .getIssuesList()
         .get(0)
         .getActions()
-        .getActionsList()
-    ).isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY));
+        .getActionsList()).isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY));
   }
 
   @Test
@@ -1048,6 +1046,234 @@ 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_pciDss32() {
+    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", "pciDss-3.2:10.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("pciDss-3.2", "10")
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(result.getIssuesList())
+      .extracting(Issue::getKey)
+      .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey());
+
+    result = ws.newRequest()
+      .setParam("pciDss-3.2", "10.1")
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(result.getIssuesList())
+      .extracting(Issue::getKey)
+      .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey());
+  }
+
+  @Test
+  public void multiple_categories_pciDss32() {
+    ComponentDto project = db.components().insertPublicProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+
+    // Rule 1
+    Consumer<RuleDto> ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto
+      .setSecurityStandards(Sets.newHashSet("cwe:20", "owaspTop10:a1", "pciDss-3.2:6.5.3", "pciDss-3.2:10.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));
+
+    // Rule 2
+    ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto
+      .setSecurityStandards(Sets.newHashSet("pciDss-4.0:6.5.3", "pciDss-3.2:1.1"))
+      .setSystemTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql"));
+    issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql"));
+    hotspotRule = db.rules().insertHotspotRule(ruleConsumer);
+    db.issues().insertHotspot(hotspotRule, project, file, issueConsumer);
+    issueRule = db.rules().insertIssueRule(ruleConsumer);
+    IssueDto issueDto3 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY));
+    IssueDto issueDto4 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY));
+
+    // Rule 3
+    ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto
+      .setSecurityStandards(Sets.newHashSet("pciDss-4.0:6.5.3", "pciDss-3.2:2.3", "pciDss-3.2:10.1.2"))
+      .setSystemTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql"));
+    issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql"));
+    hotspotRule = db.rules().insertHotspotRule(ruleConsumer);
+    db.issues().insertHotspot(hotspotRule, project, file, issueConsumer);
+    issueRule = db.rules().insertIssueRule(ruleConsumer);
+    IssueDto issueDto5 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY));
+    IssueDto issueDto6 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY));
+
+    indexPermissionsAndIssues();
+
+    SearchWsResponse result = ws.newRequest()
+      .setParam("pciDss-3.2", "1,10")
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(result.getIssuesList())
+      .extracting(Issue::getKey)
+      .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey(), issueDto3.getKey(), issueDto4.getKey(), issueDto5.getKey(), issueDto6.getKey());
+
+    result = ws.newRequest()
+      .setParam("pciDss-3.2", "1")
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(result.getIssuesList())
+      .extracting(Issue::getKey)
+      .containsExactlyInAnyOrder(issueDto3.getKey(), issueDto4.getKey());
+
+    result = ws.newRequest()
+      .setParam("pciDss-3.2", "1,10,4")
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(result.getIssuesList())
+      .extracting(Issue::getKey)
+      .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey(), issueDto3.getKey(), issueDto4.getKey(), issueDto5.getKey(), issueDto6.getKey());
+
+    result = ws.newRequest()
+      .setParam("pciDss-3.2", "4")
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(result.getIssuesList()).isEmpty();
+
+    result = ws.newRequest()
+      .setParam("pciDss-3.2", "4,7,12")
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(result.getIssuesList()).isEmpty();
+
+    result = ws.newRequest()
+      .setParam("pciDss-3.2", "10.1")
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(result.getIssuesList())
+      .extracting(Issue::getKey)
+      .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey());
+  }
+
+  @Test
+  public void only_vulnerabilities_are_returned_by_pciDss40() {
+    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-4.0:6.5.3", "pciDss-4.0:10.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("pciDss-4.0", "10,6,5")
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(result.getIssuesList())
+      .extracting(Issue::getKey)
+      .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey());
+
+    result = ws.newRequest()
+      .setParam("pciDss-4.0", "10.1,6.5,5.5")
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(result.getIssuesList())
+      .extracting(Issue::getKey)
+      .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey());
+  }
+
+  @Test
+  public void multiple_categories_pciDss40() {
+    ComponentDto project = db.components().insertPublicProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+
+    // Rule 1
+    Consumer<RuleDto> ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto
+      .setSecurityStandards(Sets.newHashSet("cwe:20", "owaspTop10:a1", "pciDss-4.0:6.5.3", "pciDss-4.0:10.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));
+
+    // Rule 2
+    ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto
+      .setSecurityStandards(Sets.newHashSet("pciDss-4.0:6.5.3", "pciDss-4.0:1.1"))
+      .setSystemTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql"));
+    issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql"));
+    hotspotRule = db.rules().insertHotspotRule(ruleConsumer);
+    db.issues().insertHotspot(hotspotRule, project, file, issueConsumer);
+    issueRule = db.rules().insertIssueRule(ruleConsumer);
+    IssueDto issueDto3 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY));
+    IssueDto issueDto4 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY));
+
+    // Rule 3
+    ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto
+      .setSecurityStandards(Sets.newHashSet("pciDss-3.2:6.5.3", "pciDss-4.0:2.3"))
+      .setSystemTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql"));
+    issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql"));
+    hotspotRule = db.rules().insertHotspotRule(ruleConsumer);
+    db.issues().insertHotspot(hotspotRule, project, file, issueConsumer);
+    issueRule = db.rules().insertIssueRule(ruleConsumer);
+    IssueDto issueDto5 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY));
+    IssueDto issueDto6 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY));
+
+    indexPermissionsAndIssues();
+
+    SearchWsResponse result = ws.newRequest()
+      .setParam("pciDss-4.0", "1,10")
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(result.getIssuesList())
+      .extracting(Issue::getKey)
+      .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey(), issueDto3.getKey(), issueDto4.getKey());
+
+    result = ws.newRequest()
+      .setParam("pciDss-4.0", "1")
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(result.getIssuesList())
+      .extracting(Issue::getKey)
+      .containsExactlyInAnyOrder(issueDto3.getKey(), issueDto4.getKey());
+
+    result = ws.newRequest()
+      .setParam("pciDss-4.0", "1,10,4")
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(result.getIssuesList())
+      .extracting(Issue::getKey)
+      .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey(), issueDto3.getKey(), issueDto4.getKey());
+
+    result = ws.newRequest()
+      .setParam("pciDss-4.0", "4")
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(result.getIssuesList()).isEmpty();
+
+    result = ws.newRequest()
+      .setParam("pciDss-4.0", "4,7,12")
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(result.getIssuesList()).isEmpty();
+  }
+
   @Test
   public void only_vulnerabilities_are_returned_by_cwe() {
     ComponentDto project = db.components().insertPublicProject();
@@ -1457,8 +1683,8 @@ 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", "owaspTop10", "owaspTop10-2021", "sansTop25",
-      "cwe", "sonarsourceSecurity", "timeZone", "inNewCodePeriod");
+      "p", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "sinceLeakPeriod", "statuses", "tags", "types", "pciDss-3.2", "pciDss-4.0", "owaspTop10",
+      "owaspTop10-2021", "sansTop25", "cwe", "sonarsourceSecurity", "timeZone", "inNewCodePeriod");
 
     WebService.Param branch = def.param(PARAM_BRANCH);
     assertThat(branch.isInternal()).isFalse();