aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien HENRY <julien.henry@sonarsource.com>2018-07-03 14:21:56 +0200
committerSonarTech <sonartech@sonarsource.com>2018-07-17 20:21:23 +0200
commitd1c074ce4e0252719f1e6801580c0a27a96b35c9 (patch)
treeb8ce70fb89ff4ec6da9d5116688fb605a7986b63
parent2a185ff22d5bbf3c19e4a890adce0ab08d2dfd5e (diff)
downloadsonarqube-d1c074ce4e0252719f1e6801580c0a27a96b35c9.tar.gz
sonarqube-d1c074ce4e0252719f1e6801580c0a27a96b35c9.zip
SONAR-10980 Index security standards in ES and update issues/search WS
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/IssueQuery.java36
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java33
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java30
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndex.java15
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java6
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java48
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueQueryTest.java12
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java22
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java29
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java2
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java5
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<String> languages;
private final Collection<String> tags;
private final Collection<String> types;
+ private final Collection<String> owaspTop10;
+ private final Collection<String> sansTop25;
+ private final Collection<String> cwe;
private final Map<String, PeriodStart> 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<String> owaspTop10() {
+ return owaspTop10;
+ }
+
+ public Collection<String> sansTop25() {
+ return sansTop25;
+ }
+
+ public Collection<String> cwe() {
+ return cwe;
+ }
+
public Map<String, PeriodStart> createdAfterByProjectUuids() {
return createdAfterByProjectUuids;
}
@@ -284,6 +302,9 @@ public class IssueQuery {
private Collection<String> languages;
private Collection<String> tags;
private Collection<String> types;
+ private Collection<String> owaspTop10;
+ private Collection<String> sansTop25;
+ private Collection<String> cwe;
private Map<String, PeriodStart> createdAfterByProjectUuids;
private Boolean onComponentOnly = false;
private Boolean assigned = null;
@@ -388,6 +409,21 @@ public class IssueQuery {
return this;
}
+ public Builder owaspTop10(@Nullable Collection<String> o) {
+ this.owaspTop10 = o;
+ return this;
+ }
+
+ public Builder sansTop25(@Nullable Collection<String> s) {
+ this.sansTop25 = s;
+ return this;
+ }
+
+ public Builder cwe(@Nullable Collection<String> cwe) {
+ this.cwe = cwe;
+ return this;
+ }
+
public Builder createdAfterByProjectUuids(@Nullable Map<String, PeriodStart> 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<String> statuses;
private List<String> tags;
private List<String> types;
+ private List<String> owaspTop10;
+ private List<String> sansTop25;
+ private List<String> cwe;
@CheckForNull
public List<String> getActionPlans() {
@@ -406,6 +409,36 @@ public class SearchRequest {
}
@CheckForNull
+ public List<String> getOwaspTop10() {
+ return owaspTop10;
+ }
+
+ public SearchRequest setOwaspTop10(@Nullable List<String> owaspTop10) {
+ this.owaspTop10 = owaspTop10;
+ return this;
+ }
+
+ @CheckForNull
+ public List<String> getSansTop25() {
+ return sansTop25;
+ }
+
+ public SearchRequest setSansTop25(@Nullable List<String> sansTop25) {
+ this.sansTop25 = sansTop25;
+ return this;
+ }
+
+ @CheckForNull
+ public List<String> getCwe() {
+ return cwe;
+ }
+
+ public SearchRequest setCwe(@Nullable List<String> cwe) {
+ this.cwe = cwe;
+ return this;
+ }
+
+ @CheckForNull
public List<String> 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<String> getOwaspTop10() {
+ return getNullableField(IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10);
+ }
+
+ public IssueDoc setOwaspTop10(@Nullable Collection<String> o) {
+ setField(IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10, o);
+ return this;
+ }
+
+ @CheckForNull
+ public Collection<String> getSansTop25() {
+ return getNullableField(IssueIndexDefinition.FIELD_ISSUE_SANS_TOP_25);
+ }
+
+ public IssueDoc setSansTop25(@Nullable Collection<String> s) {
+ setField(IssueIndexDefinition.FIELD_ISSUE_SANS_TOP_25, s);
+ return this;
+ }
+
+ @CheckForNull
+ public Collection<String> getCwe() {
+ return getNullableField(IssueIndexDefinition.FIELD_ISSUE_CWE);
+ }
+
+ public IssueDoc setCwe(@Nullable Collection<String> 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<String> INSECURE_CWE = new HashSet<>(asList("89", "78", "79", "434", "352", "601"));
+ private static final Set<String> RISKY_CWE = new HashSet<>(asList("120", "22", "494", "829", "676", "131", "134", "190"));
+ private static final Set<String> POROUS_CWE = new HashSet<>(asList("306", "862", "798", "311", "807", "250", "863", "732", "327", "307", "759"));
+ private static final Map<String, Set<String>> 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<String> standards = IssueIteratorForSingleChunk.SECURITY_STANDARDS_SPLITTER.splitToList(securityStandards == null ? "" : securityStandards);
+ List<String> 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<String> 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<String> getSansTop25(List<String> 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<String> 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<String> 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<String> 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,