From d1c074ce4e0252719f1e6801580c0a27a96b35c9 Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Tue, 3 Jul 2018 14:21:56 +0200 Subject: [PATCH] SONAR-10980 Index security standards in ES and update issues/search WS --- .../org/sonar/server/issue/IssueQuery.java | 36 ++++++++++++++ .../org/sonar/server/issue/SearchRequest.java | 33 +++++++++++++ .../sonar/server/issue/index/IssueDoc.java | 30 ++++++++++++ .../sonar/server/issue/index/IssueIndex.java | 15 ++++++ .../issue/index/IssueIndexDefinition.java | 6 +++ .../index/IssueIteratorForSingleChunk.java | 48 +++++++++++++++++-- .../sonar/server/issue/IssueQueryTest.java | 12 +++++ .../server/issue/index/IssueIndexerTest.java | 22 +++++++++ .../sonar/server/issue/ws/SearchAction.java | 29 ++++++++++- .../server/issue/ws/SearchActionTest.java | 2 +- .../ws/client/issue/IssuesWsParameters.java | 5 +- 11 files changed, 230 insertions(+), 8 deletions(-) diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/IssueQuery.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/IssueQuery.java index 33ce0641785..86b3400e411 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/IssueQuery.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/IssueQuery.java @@ -74,6 +74,9 @@ public class IssueQuery { private final Collection languages; private final Collection tags; private final Collection types; + private final Collection owaspTop10; + private final Collection sansTop25; + private final Collection cwe; private final Map createdAfterByProjectUuids; private final Boolean onComponentOnly; private final Boolean assigned; @@ -107,6 +110,9 @@ public class IssueQuery { this.languages = defaultCollection(builder.languages); this.tags = defaultCollection(builder.tags); this.types = defaultCollection(builder.types); + this.owaspTop10 = defaultCollection(builder.owaspTop10); + this.sansTop25 = defaultCollection(builder.sansTop25); + this.cwe = defaultCollection(builder.cwe); this.createdAfterByProjectUuids = defaultMap(builder.createdAfterByProjectUuids); this.onComponentOnly = builder.onComponentOnly; this.assigned = builder.assigned; @@ -191,6 +197,18 @@ public class IssueQuery { return types; } + public Collection owaspTop10() { + return owaspTop10; + } + + public Collection sansTop25() { + return sansTop25; + } + + public Collection cwe() { + return cwe; + } + public Map createdAfterByProjectUuids() { return createdAfterByProjectUuids; } @@ -284,6 +302,9 @@ public class IssueQuery { private Collection languages; private Collection tags; private Collection types; + private Collection owaspTop10; + private Collection sansTop25; + private Collection cwe; private Map createdAfterByProjectUuids; private Boolean onComponentOnly = false; private Boolean assigned = null; @@ -388,6 +409,21 @@ public class IssueQuery { return this; } + public Builder owaspTop10(@Nullable Collection o) { + this.owaspTop10 = o; + return this; + } + + public Builder sansTop25(@Nullable Collection s) { + this.sansTop25 = s; + return this; + } + + public Builder cwe(@Nullable Collection cwe) { + this.cwe = cwe; + return this; + } + public Builder createdAfterByProjectUuids(@Nullable Map createdAfterByProjectUuids) { this.createdAfterByProjectUuids = createdAfterByProjectUuids; return this; diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java index b3fb9e7a792..c421627ca7b 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java @@ -64,6 +64,9 @@ public class SearchRequest { private List statuses; private List tags; private List types; + private List owaspTop10; + private List sansTop25; + private List cwe; @CheckForNull public List getActionPlans() { @@ -405,6 +408,36 @@ public class SearchRequest { return this; } + @CheckForNull + public List getOwaspTop10() { + return owaspTop10; + } + + public SearchRequest setOwaspTop10(@Nullable List owaspTop10) { + this.owaspTop10 = owaspTop10; + return this; + } + + @CheckForNull + public List getSansTop25() { + return sansTop25; + } + + public SearchRequest setSansTop25(@Nullable List sansTop25) { + this.sansTop25 = sansTop25; + return this; + } + + @CheckForNull + public List getCwe() { + return cwe; + } + + public SearchRequest setCwe(@Nullable List cwe) { + this.cwe = cwe; + return this; + } + @CheckForNull public List getComponentRootUuids() { return componentRootUuids; diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java index 88fc40c7a93..85ee703388a 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java @@ -290,4 +290,34 @@ public class IssueDoc extends BaseDoc { return this; } + @CheckForNull + public Collection getOwaspTop10() { + return getNullableField(IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10); + } + + public IssueDoc setOwaspTop10(@Nullable Collection o) { + setField(IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10, o); + return this; + } + + @CheckForNull + public Collection getSansTop25() { + return getNullableField(IssueIndexDefinition.FIELD_ISSUE_SANS_TOP_25); + } + + public IssueDoc setSansTop25(@Nullable Collection s) { + setField(IssueIndexDefinition.FIELD_ISSUE_SANS_TOP_25, s); + return this; + } + + @CheckForNull + public Collection getCwe() { + return getNullableField(IssueIndexDefinition.FIELD_ISSUE_CWE); + } + + public IssueDoc setCwe(@Nullable Collection c) { + setField(IssueIndexDefinition.FIELD_ISSUE_CWE, c); + return this; + } + } diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndex.java index 6c015fba1ff..9f1bc7382e1 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndex.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndex.java @@ -104,14 +104,17 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_AUTHORS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AT; +import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CWE; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_DIRECTORIES; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILE_UUIDS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_LANGUAGES; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_MODULE_UUIDS; +import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECT_UUIDS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_REPORTERS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLUTIONS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES; +import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SEVERITIES; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TAGS; @@ -140,6 +143,9 @@ public class IssueIndex { PARAM_LANGUAGES, PARAM_TAGS, PARAM_TYPES, + PARAM_OWASP_TOP_10, + PARAM_SANS_TOP_25, + PARAM_CWE, PARAM_CREATED_AT); public static final String AGGREGATION_NAME_FOR_TAGS = "tags__issues"; private static final String SUBSTRING_MATCH_REGEXP = ".*%s.*"; @@ -522,6 +528,15 @@ public class IssueIndex { if (options.getFacets().contains(PARAM_TYPES)) { esSearch.addAggregation(stickyFacetBuilder.buildStickyFacet(IssueIndexDefinition.FIELD_ISSUE_TYPE, PARAM_TYPES, query.types().toArray())); } + if (options.getFacets().contains(PARAM_OWASP_TOP_10)) { + esSearch.addAggregation(stickyFacetBuilder.buildStickyFacet(IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10, PARAM_OWASP_TOP_10, query.owaspTop10().toArray())); + } + if (options.getFacets().contains(PARAM_SANS_TOP_25)) { + esSearch.addAggregation(stickyFacetBuilder.buildStickyFacet(IssueIndexDefinition.FIELD_ISSUE_SANS_TOP_25, PARAM_SANS_TOP_25, query.sansTop25().toArray())); + } + if (options.getFacets().contains(PARAM_CWE)) { + esSearch.addAggregation(stickyFacetBuilder.buildStickyFacet(IssueIndexDefinition.FIELD_ISSUE_CWE, PARAM_CWE, query.cwe().toArray())); + } if (options.getFacets().contains(PARAM_RESOLUTIONS)) { esSearch.addAggregation(createResolutionFacet(query, filters, esQuery)); } diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java index c5100bb5698..dd13a01395a 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java @@ -92,6 +92,9 @@ public class IssueIndexDefinition implements IndexDefinition { public static final String FIELD_ISSUE_STATUS = "status"; public static final String FIELD_ISSUE_TAGS = "tags"; public static final String FIELD_ISSUE_TYPE = "type"; + public static final String FIELD_ISSUE_OWASP_TOP_10 = "owaspTop10"; + public static final String FIELD_ISSUE_SANS_TOP_25 = "sansTop25"; + public static final String FIELD_ISSUE_CWE = "cwe"; private final Configuration config; private final boolean enableSource; @@ -151,5 +154,8 @@ public class IssueIndexDefinition implements IndexDefinition { type.keywordFieldBuilder(FIELD_ISSUE_STATUS).disableNorms().addSubFields(SORTABLE_ANALYZER).build(); type.keywordFieldBuilder(FIELD_ISSUE_TAGS).disableNorms().build(); type.keywordFieldBuilder(FIELD_ISSUE_TYPE).disableNorms().build(); + type.keywordFieldBuilder(FIELD_ISSUE_OWASP_TOP_10).disableNorms().build(); + type.keywordFieldBuilder(FIELD_ISSUE_SANS_TOP_25).disableNorms().build(); + type.keywordFieldBuilder(FIELD_ISSUE_CWE).disableNorms().build(); } } diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java index 8eba1043603..e2fd803ba2c 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java @@ -21,13 +21,18 @@ package org.sonar.server.issue.index; import com.google.common.base.CharMatcher; import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterators; import com.google.common.collect.Maps; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; import javax.annotation.CheckForNull; @@ -41,8 +46,14 @@ import org.sonar.db.DbSession; import org.sonar.db.ResultSetIterator; import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; import static org.sonar.api.utils.DateUtils.longToDate; import static org.sonar.db.DatabaseUtils.getLong; +import static org.sonar.server.issue.ws.SearchAction.SANS_TOP_25_INSECURE_INTERACTION; +import static org.sonar.server.issue.ws.SearchAction.SANS_TOP_25_POROUS_DEFENSES; +import static org.sonar.server.issue.ws.SearchAction.SANS_TOP_25_RISKY_RESOURCE; +import static org.sonar.server.issue.ws.SearchAction.UNKNOWN_STANDARD; /** * Scrolls over table ISSUES and reads documents to populate @@ -73,11 +84,12 @@ class IssueIteratorForSingleChunk implements IssueIterator { "c.scope", "c.organization_uuid", "c.project_uuid", + "c.main_branch_project_uuid", // column 21 - "c.main_branch_project_uuid", "i.tags", - "i.issue_type" + "i.issue_type", + "r.security_standards" }; private static final String SQL_ALL = "select " + StringUtils.join(FIELDS, ",") + " from issues i " + @@ -89,7 +101,19 @@ class IssueIteratorForSingleChunk implements IssueIterator { private static final String ISSUE_KEY_FILTER_SUFFIX = ")"; static final Splitter TAGS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings(); + static final Splitter SECURITY_STANDARDS_SPLITTER = TAGS_SPLITTER; static final Splitter MODULE_PATH_SPLITTER = Splitter.on('.').trimResults().omitEmptyStrings(); + private static final String OWASP_TOP10_PREFIX = "owaspTop10:"; + private static final String CWE_PREFIX = "cwe:"; + + // See https://www.sans.org/top25-software-errors + private static final Set INSECURE_CWE = new HashSet<>(asList("89", "78", "79", "434", "352", "601")); + private static final Set RISKY_CWE = new HashSet<>(asList("120", "22", "494", "829", "676", "131", "134", "190")); + private static final Set POROUS_CWE = new HashSet<>(asList("306", "862", "798", "311", "807", "250", "863", "732", "327", "307", "759")); + private static final Map> SANS_TOP_25_CWE_MAPPING = ImmutableMap.of( + SANS_TOP_25_INSECURE_INTERACTION, INSECURE_CWE, + SANS_TOP_25_RISKY_RESOURCE, RISKY_CWE, + SANS_TOP_25_POROUS_DEFENSES, POROUS_CWE); private final DbSession session; @@ -223,11 +247,27 @@ class IssueIteratorForSingleChunk implements IssueIterator { doc.setIsMainBranch(false); } String tags = rs.getString(21); - doc.setTags(ImmutableList.copyOf(IssueIteratorForSingleChunk.TAGS_SPLITTER.split(tags == null ? "" : tags))); + doc.setTags(IssueIteratorForSingleChunk.TAGS_SPLITTER.splitToList(tags == null ? "" : tags)); doc.setType(RuleType.valueOf(rs.getInt(22))); + String securityStandards = rs.getString(23); + + List standards = IssueIteratorForSingleChunk.SECURITY_STANDARDS_SPLITTER.splitToList(securityStandards == null ? "" : securityStandards); + List owaspTop10 = standards.stream().filter(s -> s.startsWith(OWASP_TOP10_PREFIX)).map(s -> s.substring(OWASP_TOP10_PREFIX.length())).collect(toList()); + doc.setOwaspTop10(owaspTop10.isEmpty() ? Collections.singletonList(UNKNOWN_STANDARD) : owaspTop10); + List cwe = standards.stream().filter(s -> s.startsWith(CWE_PREFIX)).map(s -> s.substring(CWE_PREFIX.length())).collect(toList()); + doc.setCwe(cwe.isEmpty() ? Collections.singletonList(UNKNOWN_STANDARD) : cwe); + doc.setSansTop25(getSansTop25(cwe)); return doc; } + private static List getSansTop25(List cwe) { + return SANS_TOP_25_CWE_MAPPING + .keySet() + .stream() + .filter(k -> cwe.stream().anyMatch(SANS_TOP_25_CWE_MAPPING.get(k)::contains)) + .collect(toList()); + } + @CheckForNull private static String extractDirPath(@Nullable String filePath, String scope) { if (filePath != null) { diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueQueryTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueQueryTest.java index 1dbec620265..957de3d743f 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueQueryTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueQueryTest.java @@ -52,6 +52,9 @@ public class IssueQueryTest { .languages(newArrayList("xoo")) .tags(newArrayList("tag1", "tag2")) .types(newArrayList("RELIABILITY", "SECURITY")) + .owaspTop10(newArrayList("a1", "a2")) + .sansTop25(newArrayList("insecure-interaction", "porous-defenses")) + .cwe(newArrayList("12", "125")) .organizationUuid("orga") .branchUuid("my_branch") .createdAfterByProjectUuids(ImmutableMap.of("PROJECT", filterDate)) @@ -74,6 +77,9 @@ public class IssueQueryTest { assertThat(query.languages()).containsOnly("xoo"); assertThat(query.tags()).containsOnly("tag1", "tag2"); assertThat(query.types()).containsOnly("RELIABILITY", "SECURITY"); + assertThat(query.owaspTop10()).containsOnly("a1", "a2"); + assertThat(query.sansTop25()).containsOnly("insecure-interaction", "porous-defenses"); + assertThat(query.cwe()).containsOnly("12", "125"); assertThat(query.organizationUuid()).isEqualTo("orga"); assertThat(query.branchUuid()).isEqualTo("my_branch"); assertThat(query.createdAfterByProjectUuids()).containsOnly(entry("PROJECT", filterDate)); @@ -128,6 +134,9 @@ public class IssueQueryTest { .languages(null) .tags(null) .types(null) + .owaspTop10(null) + .sansTop25(null) + .cwe(null) .createdAfterByProjectUuids(null) .build(); assertThat(query.issueKeys()).isEmpty(); @@ -142,6 +151,9 @@ public class IssueQueryTest { assertThat(query.languages()).isEmpty(); assertThat(query.tags()).isEmpty(); assertThat(query.types()).isEmpty(); + assertThat(query.owaspTop10()).isEmpty(); + assertThat(query.sansTop25()).isEmpty(); + assertThat(query.cwe()).isEmpty(); assertThat(query.createdAfterByProjectUuids()).isEmpty(); } diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java index 6cf54630bdb..1b5310ca460 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java @@ -22,6 +22,7 @@ package org.sonar.server.issue.index; import java.util.Arrays; import java.util.Collection; import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -56,6 +57,8 @@ import static org.junit.rules.ExpectedException.none; import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.server.issue.IssueDocTesting.newDoc; import static org.sonar.server.issue.index.IssueIndexDefinition.INDEX_TYPE_ISSUE; +import static org.sonar.server.issue.ws.SearchAction.SANS_TOP_25_POROUS_DEFENSES; +import static org.sonar.server.issue.ws.SearchAction.UNKNOWN_STANDARD; import static org.sonar.server.permission.index.AuthorizationTypeSupport.TYPE_AUTHORIZATION; public class IssueIndexerTest { @@ -134,6 +137,25 @@ public class IssueIndexerTest { assertThat(doc.line()).isEqualTo(issue.getLine()); // functional date assertThat(doc.updateDate()).isEqualToIgnoringMillis(new Date(issue.getIssueUpdateTime())); + assertThat(doc.getCwe()).containsExactlyInAnyOrder(UNKNOWN_STANDARD); + assertThat(doc.getOwaspTop10()).containsExactlyInAnyOrder(UNKNOWN_STANDARD); + assertThat(doc.getSansTop25()).isEmpty(); + } + + @Test + public void verify_security_standards_indexation() { + RuleDefinitionDto rule = db.rules().insert(r -> r.setSecurityStandards(new HashSet<>(Arrays.asList("cwe:123,owaspTop10:a3,cwe:863")))); + ComponentDto project = db.components().insertPrivateProject(organization); + ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo")); + ComponentDto file = db.components().insertComponent(newFileDto(project, dir, "F1")); + IssueDto issue = db.issues().insertIssue(IssueTesting.newIssue(rule, project, file)); + + underTest.indexOnStartup(emptySet()); + + IssueDoc doc = es.getDocuments(INDEX_TYPE_ISSUE, IssueDoc.class).get(0); + assertThat(doc.getCwe()).containsExactlyInAnyOrder("123", "863"); + assertThat(doc.getOwaspTop10()).containsExactlyInAnyOrder("a3"); + assertThat(doc.getSansTop25()).containsExactlyInAnyOrder(SANS_TOP_25_POROUS_DEFENSES); } @Test diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java index f12eb753de2..2b792c9e7f9 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java @@ -103,6 +103,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFT import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AT; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_BEFORE; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_IN_LAST; +import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CWE; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_DIRECTORIES; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILE_UUIDS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUES; @@ -110,6 +111,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_LANGUAGES; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_MODULE_UUIDS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ON_COMPONENT_ONLY; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ORGANIZATION; +import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PLANNED; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECTS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECT_KEYS; @@ -119,6 +121,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_REPORTERS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLUTIONS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLVED; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES; +import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SEVERITIES; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SINCE_LEAK_PERIOD; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES; @@ -128,6 +131,10 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TYPES; public class SearchAction implements IssuesWsAction { public static final String LOGIN_MYSELF = "__me__"; + public static final String UNKNOWN_STANDARD = "unknown"; + public static final String SANS_TOP_25_INSECURE_INTERACTION = "insecure-interaction"; + public static final String SANS_TOP_25_RISKY_RESOURCE = "risky-resource"; + public static final String SANS_TOP_25_POROUS_DEFENSES = "porous-defenses"; private static final String INTERNAL_PARAMETER_DISCLAIMER = "This parameter is mostly used by the Issues page, please prefer usage of the componentKeys parameter. "; private static final Set IGNORED_FACETS = newHashSet(PARAM_PLANNED, DEPRECATED_PARAM_ACTION_PLANS, PARAM_REPORTERS); @@ -174,7 +181,8 @@ public class SearchAction implements IssuesWsAction { new Change("5.5", "response field 'debt' is renamed 'effort'"), new Change("7.2", "response field 'externalRuleEngine' added to issues that have been imported from an external rule engine"), new Change("7.2", format("value '%s' in parameter '%s' is deprecated, it won't have any effect", SORT_BY_ASSIGNEE, Param.SORT)), - new Change("7.3", "response field 'fromHotspot' added to issues that are security hotspots")) + new Change("7.3", "response field 'fromHotspot' added to issues that are security hotspots"), + new Change("7.3", "added facets 'sansTop25', 'owaspTop10' and 'cwe'")) .setResponseExample(getClass().getResource("search-example.json")); action.addPagingParams(100, MAX_LIMIT); @@ -223,6 +231,17 @@ public class SearchAction implements IssuesWsAction { .setSince("5.5") .setPossibleValues((Object[]) RuleType.values()) .setExampleValue(format("%s,%s", RuleType.CODE_SMELL, RuleType.BUG)); + action.createParam(PARAM_OWASP_TOP_10) + .setDescription("Comma-separated list of OWASP Top 10 lowercase categories. Use '" + UNKNOWN_STANDARD + "' to select issues not associated to any OWASP Top 10 category.") + .setSince("7.3") + .setPossibleValues("a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", UNKNOWN_STANDARD); + action.createParam(PARAM_SANS_TOP_25) + .setDescription("Comma-separated list of SANS Top 25 categories.") + .setSince("7.3") + .setPossibleValues(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES); + action.createParam(PARAM_CWE) + .setDescription("Comma-separated list of CWE identifiers. Use '" + UNKNOWN_STANDARD + "' to select issues not associated to any CWE.") + .setExampleValue("12,125," + UNKNOWN_STANDARD); action.createParam(PARAM_AUTHORS) .setDescription("Comma-separated list of SCM accounts") .setExampleValue("torvalds@linux-foundation.org"); @@ -556,6 +575,9 @@ public class SearchAction implements IssuesWsAction { addMandatoryValuesToFacet(facets, PARAM_LANGUAGES, request.getLanguages()); addMandatoryValuesToFacet(facets, PARAM_TAGS, request.getTags()); addMandatoryValuesToFacet(facets, PARAM_TYPES, RuleType.names()); + addMandatoryValuesToFacet(facets, PARAM_OWASP_TOP_10, request.getOwaspTop10()); + addMandatoryValuesToFacet(facets, PARAM_SANS_TOP_25, request.getSansTop25()); + addMandatoryValuesToFacet(facets, PARAM_CWE, request.getCwe()); addMandatoryValuesToFacet(facets, PARAM_COMPONENT_UUIDS, request.getComponentUuids()); List requestedFacets = request.getFacets(); @@ -655,6 +677,9 @@ public class SearchAction implements IssuesWsAction { .setSeverities(request.paramAsStrings(PARAM_SEVERITIES)) .setStatuses(request.paramAsStrings(PARAM_STATUSES)) .setTags(request.paramAsStrings(PARAM_TAGS)) - .setTypes(request.paramAsStrings(PARAM_TYPES)); + .setTypes(request.paramAsStrings(PARAM_TYPES)) + .setOwaspTop10(request.paramAsStrings(PARAM_OWASP_TOP_10)) + .setSansTop25(request.paramAsStrings(PARAM_SANS_TOP_25)) + .setCwe(request.paramAsStrings(PARAM_CWE)); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java index f5bd488fbc4..f830a6a02df 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java @@ -148,7 +148,7 @@ public class SearchActionTest { "pullRequest", "organization", "createdAfter", "createdAt", "createdBefore", "createdInLast", "directories", "facetMode", "facets", "fileUuids", "issues", "languages", "moduleUuids", "onComponentOnly", "p", "projectUuids", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "sinceLeakPeriod", - "statuses", "tags", "types"); + "statuses", "tags", "types", "owaspTop10", "sansTop25", "cwe"); assertThat(def.param("organization")) .matches(WebService.Param::isInternal) diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java index 0032e024f4d..effe672158e 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java @@ -99,6 +99,9 @@ public class IssuesWsParameters { public static final String PARAM_LANGUAGES = "languages"; public static final String PARAM_TAGS = "tags"; public static final String PARAM_TYPES = "types"; + public static final String PARAM_OWASP_TOP_10 = "owaspTop10"; + public static final String PARAM_SANS_TOP_25 = "sansTop25"; + public static final String PARAM_CWE = "cwe"; public static final String PARAM_ASSIGNED = "assigned"; /** @@ -132,7 +135,7 @@ public class IssuesWsParameters { public static final String FACET_ASSIGNED_TO_ME = "assigned_to_me"; public static final List ALL = ImmutableList.of(PARAM_ISSUES, PARAM_SEVERITIES, PARAM_STATUSES, PARAM_RESOLUTIONS, PARAM_RESOLVED, - PARAM_COMPONENTS, PARAM_COMPONENT_ROOTS, PARAM_RULES, DEPRECATED_PARAM_ACTION_PLANS, PARAM_REPORTERS, PARAM_TAGS, PARAM_TYPES, + PARAM_COMPONENTS, PARAM_COMPONENT_ROOTS, PARAM_RULES, DEPRECATED_PARAM_ACTION_PLANS, PARAM_REPORTERS, PARAM_TAGS, PARAM_TYPES, PARAM_OWASP_TOP_10, PARAM_SANS_TOP_25, PARAM_CWE, PARAM_ASSIGNEES, PARAM_LANGUAGES, PARAM_ASSIGNED, PARAM_PLANNED, PARAM_HIDE_RULES, PARAM_CREATED_AT, PARAM_CREATED_AFTER, PARAM_CREATED_BEFORE, PARAM_CREATED_IN_LAST, PARAM_COMPONENT_UUIDS, PARAM_COMPONENT_ROOT_UUIDS, FACET_MODE, PARAM_PROJECTS, PARAM_PROJECT_UUIDS, PARAM_PROJECT_KEYS, PARAM_COMPONENT_KEYS, PARAM_MODULE_UUIDS, PARAM_DIRECTORIES, PARAM_FILE_UUIDS, PARAM_AUTHORS, -- 2.39.5