]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10980 Index security standards in ES and update issues/search WS
authorJulien HENRY <julien.henry@sonarsource.com>
Tue, 3 Jul 2018 12:21:56 +0000 (14:21 +0200)
committerSonarTech <sonartech@sonarsource.com>
Tue, 17 Jul 2018 18:21:23 +0000 (20:21 +0200)
server/sonar-server-common/src/main/java/org/sonar/server/issue/IssueQuery.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndex.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java
server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueQueryTest.java
server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java
server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java

index 33ce064178533038c0ce3eec996ad3ee8faf8e0b..86b3400e4117d8e37cf4d21743a3125dd86dcc51 100644 (file)
@@ -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;
index b3fb9e7a79245ef2f69db5b319232e5b76a02536..c421627ca7bfe701ee1497f7e1cfa021b07a2fb7 100644 (file)
@@ -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() {
@@ -405,6 +408,36 @@ public class SearchRequest {
     return this;
   }
 
+  @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;
index 88fc40c7a93ae41f9692216b2df465174ef0ce23..85ee703388a33c35d6f8f644c4419a3e1d99843e 100644 (file)
@@ -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;
+  }
+
 }
index 6c015fba1ff55186402e523ad0cfa64144ef7c8a..9f1bc7382e152ffc0cda16a2f2b86ffc3a2e3bb1 100644 (file)
@@ -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));
       }
index c5100bb569827ea6a2ecfe7305d95f7764b97bdf..dd13a01395a0098946d5b708f14b15eb3c9edfe4 100644 (file)
@@ -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();
   }
 }
index 8eba1043603e028247a767633cc4a825b4560552..e2fd803ba2c51ae06c4dae2d7a456870af60f8f8 100644 (file)
@@ -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) {
index 1dbec620265f51f7b91064ad1c21dfc7ac01b313..957de3d743fc38f7ecf3fdfd059e84b0d6eeeedb 100644 (file)
@@ -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();
   }
 
index 6cf54630bdb511b4fda97cc1d014fa36ebcd7a0d..1b5310ca460f7afc1c876cfaeba4e0c8a27c7d0b 100644 (file)
@@ -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
index f12eb753de2ba91a6f5df9395ba862bfabdea150..2b792c9e7f905d83ee1ca7e89210e929e65ff9b7 100644 (file)
@@ -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));
   }
 }
index f5bd488fbc487f21da23daab9632b8dcff70819b..f830a6a02df2029acc84c4f0f80fa1e33c0ea1cf 100644 (file)
@@ -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)
index 0032e024f4d5d7a0dd44d418c0847cecf1376a6e..effe672158ea4b98c4a4e09d3053a897299e4e9c 100644 (file)
@@ -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,