From 56826fb52dbddbe6166f7a1e80065bddc7fd254a Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 10 Aug 2020 14:45:45 +0200 Subject: [PATCH] SONAR-12459 Security Category filters vulnerability issues only --- .../sonar/server/es/StickyFacetBuilder.java | 4 +- .../sonar/server/rule/index/RuleIndex.java | 32 +++++- .../server/rule/index/RuleIndexTest.java | 34 +++--- .../sonar/server/issue/index/IssueIndex.java | 53 ++++++--- .../issue/index/IssueIndexFacetsTest.java | 68 ++++++++++- .../issue/index/IssueIndexFiltersTest.java | 54 +++++++++ .../server/issue/ws/SearchActionTest.java | 108 +++++++++++++++++- 7 files changed, 314 insertions(+), 39 deletions(-) diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/StickyFacetBuilder.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/StickyFacetBuilder.java index 9ba5e34a0f5..4fa26148c89 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/es/StickyFacetBuilder.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/es/StickyFacetBuilder.java @@ -43,7 +43,7 @@ import static org.elasticsearch.index.query.QueryBuilders.boolQuery; public class StickyFacetBuilder { private static final int FACET_DEFAULT_MIN_DOC_COUNT = 1; - private static final int FACET_DEFAULT_SIZE = 10; + public static final int FACET_DEFAULT_SIZE = 10; private static final BucketOrder FACET_DEFAULT_ORDER = BucketOrder.count(false); /** In some cases the user selects >15 items for one facet. In that case, we want to calculate the doc count for all of them (not just the first 15 items, which would be the * default for the TermsAggregation). */ @@ -67,7 +67,7 @@ public class StickyFacetBuilder { } public AggregationBuilder buildStickyFacet(String fieldName, String facetName, Object... selected) { - return buildStickyFacet(fieldName, facetName, FACET_DEFAULT_SIZE, t -> t, selected); + return buildStickyFacet(fieldName, facetName, FACET_DEFAULT_SIZE, t -> t, selected); } public AggregationBuilder buildStickyFacet(String fieldName, String facetName, int size, Object... selected) { diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java b/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java index f2c25324126..fa61b365c6b 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java @@ -77,10 +77,13 @@ import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; import static org.elasticsearch.index.query.QueryBuilders.matchPhraseQuery; import static org.elasticsearch.index.query.QueryBuilders.matchQuery; import static org.elasticsearch.index.query.QueryBuilders.termsQuery; +import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT; +import static org.sonar.api.rules.RuleType.VULNERABILITY; import static org.sonar.server.es.EsUtils.SCROLL_TIME_IN_MINUTES; import static org.sonar.server.es.EsUtils.optimizeScrollRequest; import static org.sonar.server.es.EsUtils.scrollIds; import static org.sonar.server.es.IndexType.FIELD_INDEX_TYPE; +import static org.sonar.server.es.StickyFacetBuilder.FACET_DEFAULT_SIZE; import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.ENGLISH_HTML_ANALYZER; import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER; import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SEARCH_WORDS_ANALYZER; @@ -281,22 +284,30 @@ public class RuleIndex { if (isNotEmpty(query.getCwe())) { filters.put(FIELD_RULE_CWE, - QueryBuilders.termsQuery(FIELD_RULE_CWE, query.getCwe())); + boolQuery() + .must(QueryBuilders.termsQuery(FIELD_RULE_CWE, query.getCwe())) + .must(QueryBuilders.termsQuery(FIELD_RULE_TYPE, VULNERABILITY.name(), SECURITY_HOTSPOT.name()))); } if (isNotEmpty(query.getOwaspTop10())) { filters.put(FIELD_RULE_OWASP_TOP_10, - QueryBuilders.termsQuery(FIELD_RULE_OWASP_TOP_10, query.getOwaspTop10())); + boolQuery() + .must(QueryBuilders.termsQuery(FIELD_RULE_OWASP_TOP_10, query.getOwaspTop10())) + .must(QueryBuilders.termsQuery(FIELD_RULE_TYPE, VULNERABILITY.name(), SECURITY_HOTSPOT.name()))); } if (isNotEmpty(query.getSansTop25())) { filters.put(FIELD_RULE_SANS_TOP_25, - QueryBuilders.termsQuery(FIELD_RULE_SANS_TOP_25, query.getSansTop25())); + boolQuery() + .must(QueryBuilders.termsQuery(FIELD_RULE_SANS_TOP_25, query.getSansTop25())) + .must(QueryBuilders.termsQuery(FIELD_RULE_TYPE, VULNERABILITY.name(), SECURITY_HOTSPOT.name()))); } if (isNotEmpty(query.getSonarsourceSecurity())) { filters.put(FIELD_RULE_SONARSOURCE_SECURITY, - QueryBuilders.termsQuery(FIELD_RULE_SONARSOURCE_SECURITY, query.getSonarsourceSecurity())); + boolQuery() + .must(QueryBuilders.termsQuery(FIELD_RULE_SONARSOURCE_SECURITY, query.getSonarsourceSecurity())) + .must(QueryBuilders.termsQuery(FIELD_RULE_TYPE, VULNERABILITY.name(), SECURITY_HOTSPOT.name()))); } if (StringUtils.isNotEmpty(query.getKey())) { @@ -484,30 +495,43 @@ public class RuleIndex { addDefaultSecurityFacets(query, options, aggregations, stickyFacetBuilder); } + private static Function filterSecurityCategories() { + return termsAggregation -> AggregationBuilders.filter( + "filter_by_rule_types_" + termsAggregation.getName(), + termsQuery(FIELD_RULE_TYPE, + VULNERABILITY.name(), + SECURITY_HOTSPOT.name())) + .subAggregation(termsAggregation); + } + private static void addDefaultSecurityFacets(RuleQuery query, SearchOptions options, Map aggregations, StickyFacetBuilder stickyFacetBuilder) { if (options.getFacets().contains(FACET_CWE)) { Collection categories = query.getCwe(); aggregations.put(FACET_CWE, stickyFacetBuilder.buildStickyFacet(FIELD_RULE_CWE, FACET_CWE, + FACET_DEFAULT_SIZE, filterSecurityCategories(), (categories == null) ? (new String[0]) : categories.toArray())); } if (options.getFacets().contains(FACET_OWASP_TOP_10)) { Collection categories = query.getOwaspTop10(); aggregations.put(FACET_OWASP_TOP_10, stickyFacetBuilder.buildStickyFacet(FIELD_RULE_OWASP_TOP_10, FACET_OWASP_TOP_10, + FACET_DEFAULT_SIZE, filterSecurityCategories(), (categories == null) ? (new String[0]) : categories.toArray())); } if (options.getFacets().contains(FACET_SANS_TOP_25)) { Collection categories = query.getSansTop25(); aggregations.put(FACET_SANS_TOP_25, stickyFacetBuilder.buildStickyFacet(FIELD_RULE_SANS_TOP_25, FACET_SANS_TOP_25, + FACET_DEFAULT_SIZE, filterSecurityCategories(), (categories == null) ? (new String[0]) : categories.toArray())); } if (options.getFacets().contains(FACET_SONARSOURCE_SECURITY)) { Collection categories = query.getSonarsourceSecurity(); aggregations.put(FACET_SONARSOURCE_SECURITY, stickyFacetBuilder.buildStickyFacet(FIELD_RULE_SONARSOURCE_SECURITY, FACET_SONARSOURCE_SECURITY, + FACET_DEFAULT_SIZE, filterSecurityCategories(), (categories == null) ? (new String[0]) : categories.toArray())); } } diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java index 87f92a06403..2dc3371373c 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java @@ -31,6 +31,7 @@ import org.junit.rules.ExpectedException; import org.sonar.api.impl.utils.AlwaysIncreasingSystem2; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.RuleStatus; +import org.sonar.api.rules.RuleType; import org.sonar.api.utils.System2; import org.sonar.db.DbTester; import org.sonar.db.organization.OrganizationDto; @@ -59,6 +60,7 @@ import static org.sonar.api.rule.Severity.MAJOR; import static org.sonar.api.rule.Severity.MINOR; import static org.sonar.api.rules.RuleType.BUG; import static org.sonar.api.rules.RuleType.CODE_SMELL; +import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT; import static org.sonar.api.rules.RuleType.VULNERABILITY; import static org.sonar.db.rule.RuleTesting.setCreatedAt; import static org.sonar.db.rule.RuleTesting.setIsExternal; @@ -455,10 +457,10 @@ public class RuleIndexTest { } @Test - public void search_by_security_cwe() { - RuleDefinitionDto rule1 = createRule(setSecurityStandards(of("cwe:543", "cwe:123", "owaspTop10:a1"))); - RuleDefinitionDto rule2 = createRule(setSecurityStandards(of("cwe:543", "owaspTop10:a1"))); - createRule(setSecurityStandards(of("owaspTop10:a1"))); + public void search_by_security_cwe_return_vulnerabilities_and_hotspots_only() { + RuleDefinitionDto rule1 = createRule(setSecurityStandards(of("cwe:543", "cwe:123", "owaspTop10:a1")), r -> r.setType(VULNERABILITY)); + RuleDefinitionDto rule2 = createRule(setSecurityStandards(of("cwe:543", "owaspTop10:a1")), r -> r.setType(SECURITY_HOTSPOT)); + createRule(setSecurityStandards(of("owaspTop10:a1")), r -> r.setType(CODE_SMELL)); index(); RuleQuery query = new RuleQuery().setCwe(of("543")); @@ -467,10 +469,10 @@ public class RuleIndexTest { } @Test - public void search_by_security_owaspTop10() { - RuleDefinitionDto rule1 = createRule(setSecurityStandards(of("owaspTop10:a1", "owaspTop10:a10", "cwe:543"))); - RuleDefinitionDto rule2 = createRule(setSecurityStandards(of("owaspTop10:a10", "cwe:543"))); - createRule(setSecurityStandards(of("cwe:543"))); + public void search_by_security_owaspTop10_return_vulnerabilities_and_hotspots_only() { + RuleDefinitionDto rule1 = createRule(setSecurityStandards(of("owaspTop10:a1", "owaspTop10:a10", "cwe:543")), r -> r.setType(VULNERABILITY)); + RuleDefinitionDto rule2 = createRule(setSecurityStandards(of("owaspTop10:a10", "cwe:543")), r -> r.setType(SECURITY_HOTSPOT)); + createRule(setSecurityStandards(of("cwe:543")), r -> r.setType(CODE_SMELL)); index(); RuleQuery query = new RuleQuery().setOwaspTop10(of("a5", "a10")); @@ -479,10 +481,10 @@ public class RuleIndexTest { } @Test - public void search_by_security_sansTop25() { - RuleDefinitionDto rule1 = createRule(setSecurityStandards(of("owaspTop10:a1", "owaspTop10:a10", "cwe:89"))); - RuleDefinitionDto rule2 = createRule(setSecurityStandards(of("owaspTop10:a10", "cwe:829"))); - createRule(setSecurityStandards(of("cwe:306"))); + public void search_by_security_sansTop25_return_vulnerabilities_and_hotspots_only() { + RuleDefinitionDto rule1 = createRule(setSecurityStandards(of("owaspTop10:a1", "owaspTop10:a10", "cwe:89")), r -> r.setType(VULNERABILITY)); + RuleDefinitionDto rule2 = createRule(setSecurityStandards(of("owaspTop10:a10", "cwe:829")), r -> r.setType(SECURITY_HOTSPOT)); + createRule(setSecurityStandards(of("cwe:306")), r -> r.setType(CODE_SMELL)); index(); RuleQuery query = new RuleQuery().setSansTop25(of(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)); @@ -491,10 +493,10 @@ public class RuleIndexTest { } @Test - public void search_by_security_sonarsource() { - RuleDefinitionDto rule1 = createRule(setSecurityStandards(of("owaspTop10:a1", "owaspTop10:a10", "cwe:89"))); - createRule(setSecurityStandards(of("owaspTop10:a10", "cwe:829"))); - RuleDefinitionDto rule3 = createRule(setSecurityStandards(of("cwe:601"))); + public void search_by_security_sonarsource_return_vulnerabilities_and_hotspots_only() { + RuleDefinitionDto rule1 = createRule(setSecurityStandards(of("owaspTop10:a1", "owaspTop10:a10", "cwe:89")), r -> r.setType(VULNERABILITY)); + createRule(setSecurityStandards(of("owaspTop10:a10", "cwe:829")), r -> r.setType(CODE_SMELL)); + RuleDefinitionDto rule3 = createRule(setSecurityStandards(of("cwe:601")), r -> r.setType(SECURITY_HOTSPOT)); index(); RuleQuery query = new RuleQuery().setSonarsourceSecurity(of("sql-injection", "open-redirect")); diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java index fc652c7b454..db39fc3028e 100644 --- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java +++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java @@ -433,17 +433,14 @@ public class IssueIndex { filters.addFilter( FIELD_ISSUE_ORGANIZATION_UUID, new SimpleFieldFilterScope(FIELD_ISSUE_ORGANIZATION_UUID), createTermFilter(FIELD_ISSUE_ORGANIZATION_UUID, query.organizationUuid())); - filters.addFilter( - FIELD_ISSUE_OWASP_TOP_10, OWASP_TOP_10.getFilterScope(), - createTermsFilter(FIELD_ISSUE_OWASP_TOP_10, query.owaspTop10())); - filters.addFilter( - FIELD_ISSUE_SANS_TOP_25, SANS_TOP_25.getFilterScope(), - createTermsFilter(FIELD_ISSUE_SANS_TOP_25, query.sansTop25())); - filters.addFilter(FIELD_ISSUE_CWE, CWE.getFilterScope(), createTermsFilter(FIELD_ISSUE_CWE, query.cwe())); + + // security category + addSecurityCategoryFilter(FIELD_ISSUE_OWASP_TOP_10, OWASP_TOP_10, query.owaspTop10(), filters); + addSecurityCategoryFilter(FIELD_ISSUE_SANS_TOP_25, SANS_TOP_25, query.sansTop25(), filters); + addSecurityCategoryFilter(FIELD_ISSUE_CWE, CWE, query.cwe(), filters); + addSecurityCategoryFilter(FIELD_ISSUE_SQ_SECURITY_CATEGORY, SONARSOURCE_SECURITY, query.sonarsourceSecurity(), filters); + addSeverityFilter(query, filters); - filters.addFilter( - FIELD_ISSUE_SQ_SECURITY_CATEGORY, SONARSOURCE_SECURITY.getFilterScope(), - createTermsFilter(FIELD_ISSUE_SQ_SECURITY_CATEGORY, query.sonarsourceSecurity())); addComponentRelatedFilters(query, filters); addDatesFilter(filters, query); @@ -451,6 +448,18 @@ public class IssueIndex { return filters; } + private static void addSecurityCategoryFilter(String fieldName, Facet facet, Collection values, AllFilters allFilters) { + QueryBuilder securityCategoryFilter = createTermsFilter(fieldName, values); + if (securityCategoryFilter != null) { + allFilters.addFilter( + fieldName, + facet.getFilterScope(), + boolQuery() + .must(securityCategoryFilter) + .must(termQuery(FIELD_ISSUE_TYPE, VULNERABILITY.name()))); + } + } + private static void addSeverityFilter(IssueQuery query, AllFilters allFilters) { QueryBuilder severityFieldFilter = createTermsFilter(FIELD_ISSUE_SEVERITY, query.severities()); if (severityFieldFilter != null) { @@ -659,10 +668,12 @@ public class IssueIndex { addFacetIfNeeded(options, aggregationHelper, esRequest, AUTHOR, query.authors().toArray()); addFacetIfNeeded(options, aggregationHelper, esRequest, TAGS, query.tags().toArray()); addFacetIfNeeded(options, aggregationHelper, esRequest, TYPES, query.types().toArray()); - addFacetIfNeeded(options, aggregationHelper, esRequest, OWASP_TOP_10, query.owaspTop10().toArray()); - addFacetIfNeeded(options, aggregationHelper, esRequest, SANS_TOP_25, query.sansTop25().toArray()); - addFacetIfNeeded(options, aggregationHelper, esRequest, CWE, query.cwe().toArray()); - addFacetIfNeeded(options, aggregationHelper, esRequest, SONARSOURCE_SECURITY, query.sonarsourceSecurity().toArray()); + + addSecurityCategoryFacetIfNeeded(PARAM_OWASP_TOP_10, OWASP_TOP_10, options, aggregationHelper, esRequest, query.owaspTop10().toArray()); + addSecurityCategoryFacetIfNeeded(PARAM_SANS_TOP_25, SANS_TOP_25, options, aggregationHelper, esRequest, query.sansTop25().toArray()); + addSecurityCategoryFacetIfNeeded(PARAM_CWE, CWE, options, aggregationHelper, esRequest, query.cwe().toArray()); + addSecurityCategoryFacetIfNeeded(PARAM_SONARSOURCE_SECURITY, SONARSOURCE_SECURITY, options, aggregationHelper, esRequest, query.sonarsourceSecurity().toArray()); + addSeverityFacetIfNeeded(options, aggregationHelper, esRequest); addResolutionFacetIfNeeded(options, query, aggregationHelper, esRequest); addAssigneesFacetIfNeeded(options, query, aggregationHelper, esRequest); @@ -685,6 +696,20 @@ public class IssueIndex { esRequest.addAggregation(topAggregation); } + private static void addSecurityCategoryFacetIfNeeded(String param, Facet facet, SearchOptions options, TopAggregationHelper aggregationHelper, SearchRequestBuilder esRequest, + Object[] selectedValues) { + if (!options.getFacets().contains(param)) { + return; + } + + AggregationBuilder aggregation = aggregationHelper.buildTermTopAggregation( + facet.getName(), facet.getTopAggregationDef(), facet.getNumberOfTerms(), + filter -> filter.must(termQuery(FIELD_ISSUE_TYPE, VULNERABILITY.name())), + t -> aggregationHelper.getSubAggregationHelper().buildSelectedItemsAggregation(facet.getName(), facet.getTopAggregationDef(), selectedValues) + .ifPresent(t::subAggregation)); + esRequest.addAggregation(aggregation); + } + private static void addSeverityFacetIfNeeded(SearchOptions options, TopAggregationHelper aggregationHelper, SearchRequestBuilder esRequest) { if (!options.getFacets().contains(PARAM_SEVERITIES)) { return; diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java index b735f379a4f..2fe031c9355 100644 --- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java +++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java @@ -24,8 +24,9 @@ import org.elasticsearch.action.search.SearchResponse; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.sonar.api.utils.System2; 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.organization.OrganizationDto; @@ -36,6 +37,7 @@ 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; @@ -169,6 +171,70 @@ public class IssueIndexFacetsTest { assertThatFacetHasSize(IssueQuery.builder().directories(asList(issue1.directoryPath(), issue2.directoryPath())).build(), "directories", 102); } + @Test + public void facets_on_cwe() { + ComponentDto project = newPrivateProjectDto(newOrganizationDto()); + ComponentDto file = newFileDto(project, null); + + indexIssues( + newDoc("I1", file).setType(RuleType.VULNERABILITY).setCwe(asList("20", "564", "89", "943")), + newDoc("I2", file).setType(RuleType.VULNERABILITY).setCwe(asList("943")), + newDoc("I3", file)); + + assertThatFacetHasOnly(IssueQuery.builder(), "cwe", + entry("943", 2L), + entry("20", 1L), + entry("564", 1L), + entry("89", 1L)); + } + + @Test + public void facets_on_owaspTop10() { + ComponentDto project = newPrivateProjectDto(newOrganizationDto()); + ComponentDto file = newFileDto(project, null); + + indexIssues( + newDoc("I1", file).setType(RuleType.VULNERABILITY).setOwaspTop10(asList("a1", "a2")), + newDoc("I2", file).setType(RuleType.VULNERABILITY).setOwaspTop10(singletonList("a3")), + newDoc("I3", file)); + + assertThatFacetHasOnly(IssueQuery.builder(), "owaspTop10", + entry("a1", 1L), + entry("a2", 1L), + entry("a3", 1L)); + } + + @Test + public void facets_on_sansTop25() { + ComponentDto project = newPrivateProjectDto(newOrganizationDto()); + ComponentDto file = newFileDto(project, null); + + indexIssues( + newDoc("I1", file).setType(RuleType.VULNERABILITY).setSansTop25(asList("porous-defenses", "risky-resource", "insecure-interaction")), + newDoc("I2", file).setType(RuleType.VULNERABILITY).setSansTop25(singletonList("porous-defenses")), + newDoc("I3", file)); + + assertThatFacetHasOnly(IssueQuery.builder(), "sansTop25", + entry("insecure-interaction", 1L), + entry("porous-defenses", 2L), + entry("risky-resource", 1L)); + } + + @Test + public void facets_on_sonarSourceSecurity() { + ComponentDto project = newPrivateProjectDto(newOrganizationDto()); + ComponentDto file = newFileDto(project, null); + + indexIssues( + newDoc("I1", file).setType(RuleType.VULNERABILITY).setSonarSourceSecurityCategory(SQCategory.BUFFER_OVERFLOW), + newDoc("I2", file).setType(RuleType.VULNERABILITY).setSonarSourceSecurityCategory(SQCategory.DOS), + newDoc("I3", file)); + + assertThatFacetHasOnly(IssueQuery.builder(), "sonarsourceSecurity", + entry("buffer-overflow", 1L), + entry("dos", 1L)); + } + @Test public void facets_on_severities() { ComponentDto project = newPrivateProjectDto(newOrganizationDto()); diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java index 3359730b847..6b61d3d86c4 100644 --- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java +++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java @@ -32,6 +32,7 @@ import org.junit.rules.ExpectedException; 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; @@ -42,6 +43,7 @@ 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; @@ -740,6 +742,58 @@ public class IssueIndexFiltersTest { assertThatSearchReturnsEmpty(query); } + @Test + public void filter_by_cwe() { + ComponentDto project = newPrivateProjectDto(newOrganizationDto()); + ComponentDto file = newFileDto(project, null); + + indexIssues( + newDoc("I1", file).setType(RuleType.VULNERABILITY).setCwe(asList("20", "564", "89", "943")), + newDoc("I2", file).setType(RuleType.VULNERABILITY).setCwe(asList("943")), + newDoc("I3", file)); + + assertThatSearchReturnsOnly(IssueQuery.builder().cwe(asList("20")), "I1"); + } + + @Test + public void filter_by_owaspTop10() { + ComponentDto project = newPrivateProjectDto(newOrganizationDto()); + ComponentDto file = newFileDto(project, null); + + indexIssues( + newDoc("I1", file).setType(RuleType.VULNERABILITY).setOwaspTop10(asList("a1", "a2")), + newDoc("I2", file).setType(RuleType.VULNERABILITY).setCwe(singletonList("a3")), + newDoc("I3", file)); + + assertThatSearchReturnsOnly(IssueQuery.builder().owaspTop10(asList("a1")), "I1"); + } + + @Test + public void filter_by_sansTop25() { + ComponentDto project = newPrivateProjectDto(newOrganizationDto()); + ComponentDto file = newFileDto(project, null); + + indexIssues( + newDoc("I1", file).setType(RuleType.VULNERABILITY).setSansTop25(asList("porous-defenses", "risky-resource", "insecure-interaction")), + newDoc("I2", file).setType(RuleType.VULNERABILITY).setSansTop25(singletonList("porous-defenses")), + newDoc("I3", file)); + + assertThatSearchReturnsOnly(IssueQuery.builder().sansTop25(asList("risky-resource")), "I1"); + } + + @Test + public void filter_by_sonarSecurity() { + ComponentDto project = newPrivateProjectDto(newOrganizationDto()); + ComponentDto file = newFileDto(project, null); + + indexIssues( + newDoc("I1", file).setType(RuleType.VULNERABILITY).setSonarSourceSecurityCategory(SQCategory.BUFFER_OVERFLOW), + newDoc("I2", file).setType(RuleType.VULNERABILITY).setSonarSourceSecurityCategory(SQCategory.DOS), + newDoc("I3", file)); + + assertThatSearchReturnsOnly(IssueQuery.builder().sonarsourceSecurity(singletonList("buffer-overflow")), "I1"); + } + private void verifyOrganizationFilter(String organizationUuid, String... expectedIssueKeys) { IssueQuery.Builder query = IssueQuery.builder().organizationUuid(organizationUuid); assertThatSearchReturnsOnly(query, expectedIssueKeys); diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java index fac01ee626a..066ed5b0c1e 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java @@ -823,6 +823,110 @@ 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_cwe() { + ComponentDto project = db.components().insertPublicProject(); + ComponentDto file = db.components().insertComponent(newFileDto(project)); + Consumer ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto + .setSecurityStandards(Sets.newHashSet("cwe:20", "cwe:564", "cwe:89", "cwe:943", "owaspTop10:a1")) + .setSystemTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); + Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); + RuleDefinitionDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); + db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); + RuleDefinitionDto 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)); + indexPermissions(); + indexIssues(); + + SearchWsResponse result = ws.newRequest() + .setParam("cwe", "20") + .executeProtobuf(SearchWsResponse.class); + + assertThat(result.getIssuesList()) + .extracting(Issue::getKey) + .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey()); + } + + @Test + public void only_vulnerabilities_are_returned_by_owasp() { + ComponentDto project = db.components().insertPublicProject(); + ComponentDto file = db.components().insertComponent(newFileDto(project)); + Consumer ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto + .setSecurityStandards(Sets.newHashSet("cwe:20", "cwe:564", "cwe:89", "cwe:943", "owaspTop10:a1")) + .setSystemTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); + Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); + RuleDefinitionDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); + db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); + RuleDefinitionDto 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)); + indexPermissions(); + indexIssues(); + + SearchWsResponse result = ws.newRequest() + .setParam("owaspTop10", "a1") + .executeProtobuf(SearchWsResponse.class); + + assertThat(result.getIssuesList()) + .extracting(Issue::getKey) + .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey()); + } + + @Test + public void only_vulnerabilities_are_returned_by_sansTop25() { + ComponentDto project = db.components().insertPublicProject(); + ComponentDto file = db.components().insertComponent(newFileDto(project)); + Consumer ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto + .setSecurityStandards(Sets.newHashSet("cwe:266", "cwe:732", "owaspTop10:a5")) + .setSystemTags(Sets.newHashSet("cert", "cwe", "owasp-a5", "sans-top25-porous")); + Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("cert", "cwe", "owasp-a5", "sans-top25-porous")); + RuleDefinitionDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); + db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); + RuleDefinitionDto 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)); + indexPermissions(); + indexIssues(); + + SearchWsResponse result = ws.newRequest() + .setParam("sansTop25", "porous-defenses") + .executeProtobuf(SearchWsResponse.class); + + assertThat(result.getIssuesList()) + .extracting(Issue::getKey) + .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey()); + } + + @Test + public void only_vulnerabilities_are_returned_by_sonarsource_security() { + ComponentDto project = db.components().insertPublicProject(); + ComponentDto file = db.components().insertComponent(newFileDto(project)); + Consumer ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto + .setSecurityStandards(Sets.newHashSet("cwe:20", "cwe:564", "cwe:89", "cwe:943", "owaspTop10:a1")) + .setSystemTags(Sets.newHashSet("cwe", "owasp-a1", "sans-top25-insecure", "sql")); + Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("cwe", "owasp-a1", "sans-top25-insecure", "sql")); + RuleDefinitionDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); + db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); + RuleDefinitionDto 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)); + indexPermissions(); + indexIssues(); + + SearchWsResponse result = ws.newRequest() + .setParam("sonarsourceSecurity", "sql-injection") + .executeProtobuf(SearchWsResponse.class); + + assertThat(result.getIssuesList()) + .extracting(Issue::getKey) + .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey()); + } + @Test public void security_hotspots_are_not_returned_by_default() { ComponentDto project = db.components().insertPublicProject(); @@ -875,8 +979,8 @@ public class SearchActionTest { RuleDefinitionDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); RuleDefinitionDto issueRule = db.rules().insertIssueRule(ruleConsumer); - IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer); - IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer); + 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)); indexPermissions(); indexIssues(); -- 2.39.5