]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11180 add security facet to api/rules/search
authorMichal Duda <michal.duda@sonarsource.com>
Tue, 23 Apr 2019 13:07:11 +0000 (15:07 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 7 May 2019 07:54:28 +0000 (09:54 +0200)
21 files changed:
server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleTesting.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/SecurityStandardHelper.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleDoc.java
server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java
server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndexDefinition.java
server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleQuery.java
server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityStandardHelper.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java
server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java
server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleQueryFactory.java
server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleWsSupport.java
server/sonar-server/src/main/java/org/sonar/server/rule/ws/RulesWsParameters.java
server/sonar-server/src/main/java/org/sonar/server/rule/ws/SearchAction.java
server/sonar-server/src/main/java/org/sonar/server/securityreport/ws/ShowAction.java
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ActivateRulesActionTest.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/DeactivateRulesActionTest.java
server/sonar-server/src/test/java/org/sonar/server/rule/ws/SearchActionTest.java

index 6e579e1ef95cef75db04393e7aacdbca1f8138bf..9df18d8672657258cb0c95b0e2282ea125ab9e76 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.db.rule;
 
 import com.google.common.collect.ImmutableSet;
 import java.util.Date;
+import java.util.Set;
 import java.util.function.Consumer;
 import javax.annotation.Nullable;
 import org.sonar.api.rule.RuleKey;
@@ -319,11 +320,15 @@ public class RuleTesting {
   public static Consumer<RuleDefinitionDto> setType(RuleType type) {
     return rule -> rule.setType(type);
   }
-  
+
   public static Consumer<RuleDefinitionDto> setIsExternal(boolean isExternal) {
     return rule -> rule.setIsExternal(isExternal);
   }
 
+  public static Consumer<RuleDefinitionDto> setSecurityStandards(Set<String> securityStandards) {
+    return rule -> rule.setSecurityStandards(securityStandards);
+  }
+
   public static Consumer<RuleDefinitionDto> setIsTemplate(boolean isTemplate) {
     return rule -> rule.setIsTemplate(isTemplate);
   }
index 022a91f5281126b162f100303abc0bc175a50c09..ee54f4a6d9f552b88a27cda99291403f5162b7b4 100644 (file)
@@ -43,11 +43,11 @@ import org.sonar.db.ResultSetIterator;
 import static com.google.common.base.Preconditions.checkArgument;
 import static org.sonar.api.utils.DateUtils.longToDate;
 import static org.sonar.db.DatabaseUtils.getLong;
-import static org.sonar.server.issue.index.SecurityStandardHelper.getCwe;
-import static org.sonar.server.issue.index.SecurityStandardHelper.getOwaspTop10;
-import static org.sonar.server.issue.index.SecurityStandardHelper.getSansTop25;
-import static org.sonar.server.issue.index.SecurityStandardHelper.getSecurityStandards;
-import static org.sonar.server.issue.index.SecurityStandardHelper.getSonarSourceSecurityCategories;
+import static org.sonar.server.security.SecurityStandardHelper.getCwe;
+import static org.sonar.server.security.SecurityStandardHelper.getOwaspTop10;
+import static org.sonar.server.security.SecurityStandardHelper.getSansTop25;
+import static org.sonar.server.security.SecurityStandardHelper.getSecurityStandards;
+import static org.sonar.server.security.SecurityStandardHelper.getSonarSourceSecurityCategories;
 
 /**
  * Scrolls over table ISSUES and reads documents to populate
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/SecurityStandardHelper.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/SecurityStandardHelper.java
deleted file mode 100644 (file)
index 8c445fb..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.issue.index;
-
-import com.google.common.base.Splitter;
-import com.google.common.collect.ImmutableMap;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import javax.annotation.Nullable;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
-import static java.util.stream.Collectors.toList;
-
-public class SecurityStandardHelper {
-
-  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 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"));
-
-
-  public static final Map<String, List<String>> SONARSOURCE_CWE_MAPPING = ImmutableMap.<String, List<String>>builder()
-    .put("sql-injection", asList("89", "564"))
-    .put("command-injection", asList("78", "77"))
-    .put("path-traversal-injection", singletonList("22"))
-    .put("ldap-injection", singletonList("90"))
-    .put("xpath-injection", singletonList("643"))
-    .put("expression-lang-injection", singletonList("917"))
-    .put("rce", singletonList("94"))
-    .put("dos", singletonList("400"))
-    .put("ssrf", singletonList("918"))
-    .put("csrf", singletonList("352"))
-    .put("xss", asList("79", "80", "81", "82", "83", "84", "85", "86", "87"))
-    .put("log-injection", singletonList("117"))
-    .put("http-response-splitting", singletonList("113"))
-    .put("open-redirect", singletonList("601"))
-    .put("xxe", asList("611", "827"))
-    .put("object-injection", singletonList("470"))
-    .put("weak-cryptography", asList("326", "295", "326", "327", "297", "780", "328", "327"))
-    .put("auth", asList("798", "640", "620", "549", "522", "521", "263", "262", "261", "259", "522", "284"))
-    .put("insecure-conf", asList("102", "489"))
-    .put("file-manipulation", asList("97", "73"))
-    .build();
-
-  public 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 static final Splitter SECURITY_STANDARDS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
-
-  private SecurityStandardHelper() {
-    // Utility class
-  }
-
-  public static List<String> getSecurityStandards(@Nullable String securityStandards) {
-    return securityStandards == null ? emptyList() : SECURITY_STANDARDS_SPLITTER.splitToList(securityStandards);
-  }
-
-  public static List<String> getSansTop25(Collection<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());
-  }
-
-  public static List<String> getSonarSourceSecurityCategories(Collection<String> cwe) {
-    return SONARSOURCE_CWE_MAPPING
-      .keySet()
-      .stream()
-      .filter(k -> cwe.stream().anyMatch(SONARSOURCE_CWE_MAPPING.get(k)::contains))
-      .collect(toList());
-  }
-
-  public static List<String> getOwaspTop10(Collection<String> securityStandards) {
-    List<String> result = securityStandards.stream()
-      .filter(s -> s.startsWith(OWASP_TOP10_PREFIX))
-      .map(s -> s.substring(OWASP_TOP10_PREFIX.length()))
-      .collect(toList());
-    return result.isEmpty() ? singletonList(UNKNOWN_STANDARD) : result;
-  }
-
-  public static List<String> getCwe(Collection<String> securityStandards) {
-    List<String> result = securityStandards.stream()
-      .filter(s -> s.startsWith(CWE_PREFIX))
-      .map(s -> s.substring(CWE_PREFIX.length()))
-      .collect(toList());
-    return result.isEmpty() ? singletonList(UNKNOWN_STANDARD) : result;
-  }
-
-  public static List<String> getSansTop25(String securityStandards) {
-    return getSansTop25(getCwe(getSecurityStandards(securityStandards)));
-  }
-
-  public static List<String> getSonarSourceSecurityCategories(String securityStandards) {
-    return getSonarSourceSecurityCategories(getCwe(getSecurityStandards(securityStandards)));
-  }
-
-  public static List<String> getOwaspTop10(String securityStandards) {
-    return getOwaspTop10(getSecurityStandards(securityStandards));
-  }
-
-  public static List<String> getCwe(String securityStandards) {
-    return getCwe(getSecurityStandards(securityStandards));
-  }
-}
index 0ab4ce887475ffe4c9cb1d8dd6ec0a04d5d06c6c..8d7b38be50d4d4ec14d7522e760afa36b9f5e579 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.server.rule.index;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Maps;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
@@ -34,6 +35,7 @@ import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleForIndexingDto;
 import org.sonar.markdown.Markdown;
 import org.sonar.server.es.BaseDoc;
+import org.sonar.server.security.SecurityStandardHelper;
 
 import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE;
 
@@ -153,6 +155,46 @@ public class RuleDoc extends BaseDoc {
     return this;
   }
 
+  @CheckForNull
+  public Collection<String> getOwaspTop10() {
+    return getNullableField(RuleIndexDefinition.FIELD_RULE_OWASP_TOP_10);
+  }
+
+  public RuleDoc setOwaspTop10(@Nullable Collection<String> o) {
+    setField(RuleIndexDefinition.FIELD_RULE_OWASP_TOP_10, o);
+    return this;
+  }
+
+  @CheckForNull
+  public Collection<String> getSansTop25() {
+    return getNullableField(RuleIndexDefinition.FIELD_RULE_SANS_TOP_25);
+  }
+
+  public RuleDoc setSansTop25(@Nullable Collection<String> s) {
+    setField(RuleIndexDefinition.FIELD_RULE_SANS_TOP_25, s);
+    return this;
+  }
+
+  @CheckForNull
+  public Collection<String> getCwe() {
+    return getNullableField(RuleIndexDefinition.FIELD_RULE_CWE);
+  }
+
+  public RuleDoc setCwe(@Nullable Collection<String> c) {
+    setField(RuleIndexDefinition.FIELD_RULE_CWE, c);
+    return this;
+  }
+
+  @CheckForNull
+  public Collection<String> getSonarSourceSecurityCategories() {
+    return getNullableField(RuleIndexDefinition.FIELD_RULE_SONARSOURCE_SECURITY);
+  }
+
+  public RuleDoc setSonarSourceSecurityCategories(@Nullable Collection<String> c) {
+    setField(RuleIndexDefinition.FIELD_RULE_SONARSOURCE_SECURITY, c);
+    return this;
+  }
+
   @CheckForNull
   public RuleStatus status() {
     return RuleStatus.valueOf(getField(RuleIndexDefinition.FIELD_RULE_STATUS));
@@ -226,6 +268,7 @@ public class RuleDoc extends BaseDoc {
   }
 
   public static RuleDoc of(RuleForIndexingDto dto) {
+    Collection<String> cwe = SecurityStandardHelper.getCwe(dto.getSecurityStandardsAsSet());
     RuleDoc ruleDoc = new RuleDoc()
       .setId(dto.getId())
       .setKey(dto.getRuleKey().toString())
@@ -234,6 +277,10 @@ public class RuleDoc extends BaseDoc {
       .setIsTemplate(dto.isTemplate())
       .setIsExternal(dto.isExternal())
       .setLanguage(dto.getLanguage())
+      .setCwe(cwe)
+      .setOwaspTop10(SecurityStandardHelper.getOwaspTop10(dto.getSecurityStandardsAsSet()))
+      .setSansTop25(SecurityStandardHelper.getSansTop25(cwe))
+      .setSonarSourceSecurityCategories(SecurityStandardHelper.getSonarSourceSecurityCategories(cwe))
       .setName(dto.getName())
       .setRuleKey(dto.getPluginRuleKey())
       .setSeverity(dto.getSeverityAsString())
index d0e3c43107f01a4d835d3031380309e181fd4a15..b26cf2e2dbd8c3bfbc3fdfcf30ec6090913f6a87 100644 (file)
@@ -89,6 +89,7 @@ import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_PROFILE_UUID;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_SEVERITY;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_CREATED_AT;
+import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_CWE;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_EXTENSION_SCOPE;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_EXTENSION_TAGS;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_HTML_DESCRIPTION;
@@ -98,9 +99,12 @@ import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_IS_TEMP
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_KEY;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_LANGUAGE;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_NAME;
+import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_OWASP_TOP_10;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_REPOSITORY;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_RULE_KEY;
+import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_SANS_TOP_25;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_SEVERITY;
+import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_SONARSOURCE_SECURITY;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_STATUS;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_TEMPLATE_KEY;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_TYPE;
@@ -123,6 +127,10 @@ public class RuleIndex {
   public static final String FACET_STATUSES = "statuses";
   public static final String FACET_TYPES = "types";
   public static final String FACET_OLD_DEFAULT = "true";
+  public static final String FACET_CWE = "cwe";
+  public static final String FACET_SANS_TOP_25 = "sansTop25";
+  public static final String FACET_OWASP_TOP_10 = "owaspTop10";
+  public static final String FACET_SONARSOURCE_SECURITY = "sonarsourceSecurity";
 
   private static final int MAX_FACET_SIZE = 100;
 
@@ -271,6 +279,26 @@ public class RuleIndex {
         QueryBuilders.termsQuery(FIELD_RULE_SEVERITY, query.getSeverities()));
     }
 
+    if (isNotEmpty(query.getCwe())) {
+      filters.put(FIELD_RULE_CWE,
+        QueryBuilders.termsQuery(FIELD_RULE_CWE, query.getCwe()));
+    }
+
+    if (isNotEmpty(query.getOwaspTop10())) {
+      filters.put(FIELD_RULE_OWASP_TOP_10,
+        QueryBuilders.termsQuery(FIELD_RULE_OWASP_TOP_10, query.getOwaspTop10()));
+    }
+
+    if (isNotEmpty(query.getSansTop25())) {
+      filters.put(FIELD_RULE_SANS_TOP_25,
+        QueryBuilders.termsQuery(FIELD_RULE_SANS_TOP_25, query.getSansTop25()));
+    }
+
+    if (isNotEmpty(query.getSonarsourceSecurity())) {
+      filters.put(FIELD_RULE_SONARSOURCE_SECURITY,
+        QueryBuilders.termsQuery(FIELD_RULE_SONARSOURCE_SECURITY, query.getSonarsourceSecurity()));
+    }
+
     if (StringUtils.isNotEmpty(query.getKey())) {
       filters.put(FIELD_RULE_KEY,
         QueryBuilders.termQuery(FIELD_RULE_KEY, query.getKey()));
@@ -452,6 +480,36 @@ public class RuleIndex {
         stickyFacetBuilder.buildStickyFacet(FIELD_RULE_REPOSITORY, FACET_REPOSITORIES,
           (repositories == null) ? (new String[0]) : repositories.toArray()));
     }
+
+    addDefaultSecurityFacets(query, options, aggregations, stickyFacetBuilder);
+  }
+
+  private static void addDefaultSecurityFacets(RuleQuery query, SearchOptions options, Map<String, AggregationBuilder> aggregations,
+    StickyFacetBuilder stickyFacetBuilder) {
+    if (options.getFacets().contains(FACET_CWE)) {
+      Collection<String> categories = query.getCwe();
+      aggregations.put(FACET_CWE,
+        stickyFacetBuilder.buildStickyFacet(FIELD_RULE_CWE, FACET_CWE,
+          (categories == null) ? (new String[0]) : categories.toArray()));
+    }
+    if (options.getFacets().contains(FACET_OWASP_TOP_10)) {
+      Collection<String> categories = query.getOwaspTop10();
+      aggregations.put(FACET_OWASP_TOP_10,
+        stickyFacetBuilder.buildStickyFacet(FIELD_RULE_OWASP_TOP_10, FACET_OWASP_TOP_10,
+          (categories == null) ? (new String[0]) : categories.toArray()));
+    }
+    if (options.getFacets().contains(FACET_SANS_TOP_25)) {
+      Collection<String> categories = query.getSansTop25();
+      aggregations.put(FACET_SANS_TOP_25,
+        stickyFacetBuilder.buildStickyFacet(FIELD_RULE_SANS_TOP_25, FACET_SANS_TOP_25,
+          (categories == null) ? (new String[0]) : categories.toArray()));
+    }
+    if (options.getFacets().contains(FACET_SONARSOURCE_SECURITY)) {
+      Collection<String> categories = query.getSonarsourceSecurity();
+      aggregations.put(FACET_SONARSOURCE_SECURITY,
+        stickyFacetBuilder.buildStickyFacet(FIELD_RULE_SONARSOURCE_SECURITY, FACET_SONARSOURCE_SECURITY,
+          (categories == null) ? (new String[0]) : categories.toArray()));
+    }
   }
 
   private static void addStatusFacetIfNeeded(SearchOptions options, Map<String, AggregationBuilder> aggregations, StickyFacetBuilder stickyFacetBuilder) {
index 4d0226a02299bce15e0355aba572371371fe9b4a..f1cf0f9ab5999d661a6c68f904a07ac2319a3e53 100644 (file)
@@ -60,6 +60,10 @@ public class RuleIndexDefinition implements IndexDefinition {
   public static final String FIELD_RULE_TYPE = "type";
   public static final String FIELD_RULE_CREATED_AT = "createdAt";
   public static final String FIELD_RULE_UPDATED_AT = "updatedAt";
+  public static final String FIELD_RULE_CWE = "cwe";
+  public static final String FIELD_RULE_OWASP_TOP_10 = "owaspTop10";
+  public static final String FIELD_RULE_SANS_TOP_25 = "sansTop25";
+  public static final String FIELD_RULE_SONARSOURCE_SECURITY = "sonarsourceSecurity";
 
   public static final Set<String> SORT_FIELDS = ImmutableSet.of(
     FIELD_RULE_NAME,
@@ -143,6 +147,11 @@ public class RuleIndexDefinition implements IndexDefinition {
     ruleMapping.createLongField(FIELD_RULE_CREATED_AT);
     ruleMapping.createLongField(FIELD_RULE_UPDATED_AT);
 
+    ruleMapping.keywordFieldBuilder(FIELD_RULE_CWE).disableNorms().build();
+    ruleMapping.keywordFieldBuilder(FIELD_RULE_OWASP_TOP_10).disableNorms().build();
+    ruleMapping.keywordFieldBuilder(FIELD_RULE_SANS_TOP_25).disableNorms().build();
+    ruleMapping.keywordFieldBuilder(FIELD_RULE_SONARSOURCE_SECURITY).disableNorms().build();
+
     // Active rule
     index.createTypeMapping(TYPE_ACTIVE_RULE)
       .keywordFieldBuilder(FIELD_ACTIVE_RULE_ID).disableNorms().build()
index 332fcb67f3d36daf18a7049deaacad02d33203b1..ae29a147fdba980ab8e4740e454fcfcac19347b6 100644 (file)
@@ -55,6 +55,10 @@ public class RuleQuery {
   private String ruleKey;
   private OrganizationDto organization;
   private boolean includeExternal;
+  private Collection<String> owaspTop10;
+  private Collection<String> sansTop25;
+  private Collection<String> cwe;
+  private Collection<String> sonarsourceSecurity;
 
   @CheckForNull
   public QProfileDto getQProfile() {
@@ -293,4 +297,44 @@ public class RuleQuery {
     this.compareToQProfile = compareToQProfile;
     return this;
   }
+
+  @CheckForNull
+  public Collection<String> getCwe() {
+    return cwe;
+  }
+
+  public RuleQuery setCwe(@Nullable Collection<String> cwe) {
+    this.cwe = cwe;
+    return this;
+  }
+
+  @CheckForNull
+  public Collection<String> getOwaspTop10() {
+    return owaspTop10;
+  }
+
+  public RuleQuery setOwaspTop10(@Nullable Collection<String> owaspTop10) {
+    this.owaspTop10 = owaspTop10;
+    return this;
+  }
+
+  @CheckForNull
+  public Collection<String> getSansTop25() {
+    return sansTop25;
+  }
+
+  public RuleQuery setSansTop25(@Nullable Collection<String> sansTop25) {
+    this.sansTop25 = sansTop25;
+    return this;
+  }
+
+  @CheckForNull
+  public Collection<String> getSonarsourceSecurity() {
+    return sonarsourceSecurity;
+  }
+
+  public RuleQuery setSonarsourceSecurity(@Nullable Collection<String> sonarsourceSecurity) {
+    this.sonarsourceSecurity = sonarsourceSecurity;
+    return this;
+  }
 }
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityStandardHelper.java b/server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityStandardHelper.java
new file mode 100644 (file)
index 0000000..d8ac726
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.security;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableMap;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nullable;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static java.util.stream.Collectors.toList;
+
+public class SecurityStandardHelper {
+
+  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 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"));
+
+  public static final Map<String, List<String>> SONARSOURCE_CWE_MAPPING = ImmutableMap.<String, List<String>>builder()
+    .put("sql-injection", asList("89", "564"))
+    .put("command-injection", asList("78", "77"))
+    .put("path-traversal-injection", singletonList("22"))
+    .put("ldap-injection", singletonList("90"))
+    .put("xpath-injection", singletonList("643"))
+    .put("expression-lang-injection", singletonList("917"))
+    .put("rce", singletonList("94"))
+    .put("dos", singletonList("400"))
+    .put("ssrf", singletonList("918"))
+    .put("csrf", singletonList("352"))
+    .put("xss", asList("79", "80", "81", "82", "83", "84", "85", "86", "87"))
+    .put("log-injection", singletonList("117"))
+    .put("http-response-splitting", singletonList("113"))
+    .put("open-redirect", singletonList("601"))
+    .put("xxe", asList("611", "827"))
+    .put("object-injection", singletonList("470"))
+    .put("weak-cryptography", asList("326", "295", "326", "327", "297", "780", "328", "327"))
+    .put("auth", asList("798", "640", "620", "549", "522", "521", "263", "262", "261", "259", "284"))
+    .put("insecure-conf", asList("102", "489"))
+    .put("file-manipulation", asList("97", "73"))
+    .build();
+
+  public 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 static final Splitter SECURITY_STANDARDS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
+
+  private SecurityStandardHelper() {
+    // Utility class
+  }
+
+  public static List<String> getSecurityStandards(@Nullable String securityStandards) {
+    return securityStandards == null ? emptyList() : SECURITY_STANDARDS_SPLITTER.splitToList(securityStandards);
+  }
+
+  public static List<String> getSansTop25(Collection<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());
+  }
+
+  public static List<String> getSonarSourceSecurityCategories(Collection<String> cwe) {
+    return SONARSOURCE_CWE_MAPPING
+      .keySet()
+      .stream()
+      .filter(k -> cwe.stream().anyMatch(SONARSOURCE_CWE_MAPPING.get(k)::contains))
+      .collect(toList());
+  }
+
+  public static List<String> getOwaspTop10(Collection<String> securityStandards) {
+    List<String> result = securityStandards.stream()
+      .filter(s -> s.startsWith(OWASP_TOP10_PREFIX))
+      .map(s -> s.substring(OWASP_TOP10_PREFIX.length()))
+      .collect(toList());
+    return result.isEmpty() ? singletonList(UNKNOWN_STANDARD) : result;
+  }
+
+  public static List<String> getCwe(Collection<String> securityStandards) {
+    List<String> result = securityStandards.stream()
+      .filter(s -> s.startsWith(CWE_PREFIX))
+      .map(s -> s.substring(CWE_PREFIX.length()))
+      .collect(toList());
+    return result.isEmpty() ? singletonList(UNKNOWN_STANDARD) : result;
+  }
+
+  public static List<String> getSansTop25(String securityStandards) {
+    return getSansTop25(getCwe(getSecurityStandards(securityStandards)));
+  }
+
+  public static List<String> getSonarSourceSecurityCategories(String securityStandards) {
+    return getSonarSourceSecurityCategories(getCwe(getSecurityStandards(securityStandards)));
+  }
+
+  public static List<String> getOwaspTop10(String securityStandards) {
+    return getOwaspTop10(getSecurityStandards(securityStandards));
+  }
+
+  public static List<String> getCwe(String securityStandards) {
+    return getCwe(getSecurityStandards(securityStandards));
+  }
+}
index 67264571c846ea995f0a5e81e1f455b33cd0287f..199237e9e4018c8ff40c50c62fbe6335232549d1 100644 (file)
@@ -57,8 +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.TYPE_ISSUE;
-import static org.sonar.server.issue.index.SecurityStandardHelper.SANS_TOP_25_POROUS_DEFENSES;
-import static org.sonar.server.issue.index.SecurityStandardHelper.UNKNOWN_STANDARD;
+import static org.sonar.server.security.SecurityStandardHelper.SANS_TOP_25_POROUS_DEFENSES;
+import static org.sonar.server.security.SecurityStandardHelper.UNKNOWN_STANDARD;
 import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE_AUTHORIZATION;
 
 public class IssueIndexerTest {
index e365428c917e9bada27089fa635a985c7c4018d5..d11ab122cadbe49b86a8cfb49379594a744cc0e1 100644 (file)
@@ -69,6 +69,7 @@ import static org.sonar.db.rule.RuleTesting.setName;
 import static org.sonar.db.rule.RuleTesting.setOrganization;
 import static org.sonar.db.rule.RuleTesting.setRepositoryKey;
 import static org.sonar.db.rule.RuleTesting.setRuleKey;
+import static org.sonar.db.rule.RuleTesting.setSecurityStandards;
 import static org.sonar.db.rule.RuleTesting.setSeverity;
 import static org.sonar.db.rule.RuleTesting.setStatus;
 import static org.sonar.db.rule.RuleTesting.setSystemTags;
@@ -85,6 +86,8 @@ import static org.sonar.server.rule.index.RuleIndex.FACET_TYPES;
 import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_ACTIVE_RULE;
 import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE;
 import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE_EXTENSION;
+import static org.sonar.server.security.SecurityStandardHelper.SANS_TOP_25_INSECURE_INTERACTION;
+import static org.sonar.server.security.SecurityStandardHelper.SANS_TOP_25_RISKY_RESOURCE;
 
 public class RuleIndexTest {
 
@@ -122,7 +125,7 @@ public class RuleIndexTest {
     RuleDefinitionDto cobol1 = createRule(
       setRepositoryKey("cobol"),
       setRuleKey("X001"));
-    RuleDefinitionDto php2 = createRule(
+    createRule(
       setRepositoryKey("php"),
       setRuleKey("S002"));
     index();
@@ -451,6 +454,54 @@ public class RuleIndexTest {
     assertThat(underTest.search(query, new SearchOptions()).getIds()).hasSize(2);
   }
 
+  @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")));
+    index();
+
+    RuleQuery query = new RuleQuery().setCwe(of("543"));
+    SearchIdResult<Integer> results = underTest.search(query, new SearchOptions().addFacets("cwe"));
+    assertThat(results.getIds()).containsOnly(rule1.getId(), rule2.getId());
+  }
+
+  @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")));
+    index();
+
+    RuleQuery query = new RuleQuery().setOwaspTop10(of("a5", "a10"));
+    SearchIdResult<Integer> results = underTest.search(query, new SearchOptions().addFacets("owaspTop10"));
+    assertThat(results.getIds()).containsOnly(rule1.getId(), rule2.getId());
+  }
+
+  @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")));
+    index();
+
+    RuleQuery query = new RuleQuery().setSansTop25(of(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE));
+    SearchIdResult<Integer> results = underTest.search(query, new SearchOptions().addFacets("sansTop25"));
+    assertThat(results.getIds()).containsOnly(rule1.getId(), rule2.getId());
+  }
+
+  @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")));
+    index();
+
+    RuleQuery query = new RuleQuery().setSonarsourceSecurity(of("sql-injection", "open-redirect"));
+    SearchIdResult<Integer> results = underTest.search(query, new SearchOptions().addFacets("sonarsourceSecurity"));
+    assertThat(results.getIds()).containsOnly(rule1.getId(), rule3.getId());
+  }
+
   @Test
   public void compare_to_another_profile() {
     String xoo = "xoo";
@@ -842,7 +893,7 @@ public class RuleIndexTest {
 
   @Test
   public void languages_facet_should_return_top_100_items() {
-    rangeClosed(1, 101).forEach(i ->  db.rules().insert(r -> r.setLanguage("lang" + i)));
+    rangeClosed(1, 101).forEach(i -> db.rules().insert(r -> r.setLanguage("lang" + i)));
     index();
 
     SearchIdResult result = underTest.search(new RuleQuery(), new SearchOptions().addFacets(singletonList(FACET_LANGUAGES)));
@@ -852,7 +903,7 @@ public class RuleIndexTest {
 
   @Test
   public void repositories_facet_should_return_top_10_items() {
-    rangeClosed(1, 11).forEach(i ->  db.rules().insert(r -> r.setRepositoryKey("repo" + i)));
+    rangeClosed(1, 11).forEach(i -> db.rules().insert(r -> r.setRepositoryKey("repo" + i)));
     index();
 
     SearchIdResult result = underTest.search(new RuleQuery(), new SearchOptions().addFacets(singletonList(FACET_REPOSITORIES)));
index 3e4d017d0d313655c11c89c410fdfaca7a8fb259..4babfa38c5a3964cf2028b66e3fea42fefb11e0a 100644 (file)
@@ -82,6 +82,7 @@ import org.sonar.server.es.StickyFacetBuilder;
 import org.sonar.server.issue.index.IssueQuery.PeriodStart;
 import org.sonar.server.permission.index.AuthorizationDoc;
 import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
+import org.sonar.server.security.SecurityStandardHelper;
 import org.sonar.server.user.UserSession;
 import org.sonar.server.view.index.ViewIndexDefinition;
 
@@ -151,10 +152,10 @@ import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_STAT
 import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_TAGS;
 import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_TYPE;
 import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
-import static org.sonar.server.issue.index.SecurityStandardHelper.SANS_TOP_25_INSECURE_INTERACTION;
-import static org.sonar.server.issue.index.SecurityStandardHelper.SANS_TOP_25_POROUS_DEFENSES;
-import static org.sonar.server.issue.index.SecurityStandardHelper.SANS_TOP_25_RISKY_RESOURCE;
-import static org.sonar.server.issue.index.SecurityStandardHelper.UNKNOWN_STANDARD;
+import static org.sonar.server.security.SecurityStandardHelper.SANS_TOP_25_INSECURE_INTERACTION;
+import static org.sonar.server.security.SecurityStandardHelper.SANS_TOP_25_POROUS_DEFENSES;
+import static org.sonar.server.security.SecurityStandardHelper.SANS_TOP_25_RISKY_RESOURCE;
+import static org.sonar.server.security.SecurityStandardHelper.UNKNOWN_STANDARD;
 import static org.sonar.server.view.index.ViewIndexDefinition.TYPE_VIEW;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.DEPRECATED_PARAM_AUTHORS;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT;
index 326651d5944fc83689e4d6b955a7a243b61778ec..1c15a9e1ea5352886ff872a70df9a77702f2a307 100644 (file)
@@ -76,11 +76,11 @@ import static org.sonar.server.issue.index.IssueIndex.FACET_ASSIGNED_TO_ME;
 import static org.sonar.server.issue.index.IssueIndex.FACET_PROJECTS;
 import static org.sonar.server.issue.index.IssueQuery.SORT_BY_ASSIGNEE;
 import static org.sonar.server.issue.index.IssueQueryFactory.UNKNOWN;
-import static org.sonar.server.issue.index.SecurityStandardHelper.SANS_TOP_25_INSECURE_INTERACTION;
-import static org.sonar.server.issue.index.SecurityStandardHelper.SANS_TOP_25_POROUS_DEFENSES;
-import static org.sonar.server.issue.index.SecurityStandardHelper.SANS_TOP_25_RISKY_RESOURCE;
-import static org.sonar.server.issue.index.SecurityStandardHelper.SONARSOURCE_CWE_MAPPING;
-import static org.sonar.server.issue.index.SecurityStandardHelper.UNKNOWN_STANDARD;
+import static org.sonar.server.security.SecurityStandardHelper.SANS_TOP_25_INSECURE_INTERACTION;
+import static org.sonar.server.security.SecurityStandardHelper.SANS_TOP_25_POROUS_DEFENSES;
+import static org.sonar.server.security.SecurityStandardHelper.SANS_TOP_25_RISKY_RESOURCE;
+import static org.sonar.server.security.SecurityStandardHelper.SONARSOURCE_CWE_MAPPING;
+import static org.sonar.server.security.SecurityStandardHelper.UNKNOWN_STANDARD;
 import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
 import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
 import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
index f0a36f68990403e7e8a09093e617e985646e9c88..35482f639be4ac203a8f6285cbea3622187ae3fb 100644 (file)
@@ -38,15 +38,19 @@ import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVATION;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVE_SEVERITIES;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_AVAILABLE_SINCE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CWE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_INCLUDE_EXTERNAL;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_INHERITANCE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_IS_TEMPLATE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_LANGUAGES;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ORGANIZATION;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_OWASP_TOP_10;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_QPROFILE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_REPOSITORIES;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_RULE_KEY;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_SANS_TOP_25;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_SEVERITIES;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_SONARSOURCE_SECURITY;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_STATUSES;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_TAGS;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_TEMPLATE_KEY;
@@ -105,6 +109,10 @@ public class RuleQueryFactory {
     query.setTemplateKey(request.param(PARAM_TEMPLATE_KEY));
     query.setTypes(toEnums(request.paramAsStrings(PARAM_TYPES), RuleType.class));
     query.setKey(request.param(PARAM_RULE_KEY));
+    query.setCwe(request.paramAsStrings(PARAM_CWE));
+    query.setOwaspTop10(request.paramAsStrings(PARAM_OWASP_TOP_10));
+    query.setSansTop25(request.paramAsStrings(PARAM_SANS_TOP_25));
+    query.setSonarsourceSecurity(request.paramAsStrings(PARAM_SONARSOURCE_SECURITY));
 
     String sortParam = request.param(WebService.Param.SORT);
     if (sortParam != null) {
index 3b2559b7d68fc6b2e06c8c0579abaf483854bd01..eb19feff8a51fb702f0e753157c8d30e3278fbf7 100644 (file)
@@ -35,6 +35,7 @@ import org.sonar.db.DbSession;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.user.UserDto;
+import org.sonar.server.security.SecurityStandardHelper;
 import org.sonar.server.organization.DefaultOrganizationProvider;
 import org.sonar.server.qualityprofile.ActiveRuleInheritance;
 import org.sonar.server.rule.index.RuleIndexDefinition;
@@ -49,19 +50,25 @@ import static org.sonar.core.util.stream.MoreCollectors.toSet;
 import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
 import static org.sonar.db.organization.OrganizationDto.Subscription.PAID;
 import static org.sonar.db.permission.OrganizationPermission.ADMINISTER_QUALITY_PROFILES;
+import static org.sonar.server.security.SecurityStandardHelper.SANS_TOP_25_CWE_MAPPING;
+import static org.sonar.server.security.SecurityStandardHelper.UNKNOWN_STANDARD;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVATION;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVE_SEVERITIES;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_AVAILABLE_SINCE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CWE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_INCLUDE_EXTERNAL;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_INHERITANCE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_IS_TEMPLATE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_LANGUAGES;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ORGANIZATION;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_OWASP_TOP_10;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_QPROFILE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_REPOSITORIES;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_RULE_KEY;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_SANS_TOP_25;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_SEVERITIES;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_SONARSOURCE_SECURITY;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_STATUSES;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_TAGS;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_TEMPLATE_KEY;
@@ -130,6 +137,28 @@ public class RuleWsSupport {
       .setPossibleValues(Severity.ALL)
       .setExampleValue("CRITICAL,BLOCKER");
 
+    action
+      .createParam(PARAM_CWE)
+      .setDescription("Comma-separated list of CWE identifiers. Use '" + UNKNOWN_STANDARD + "' to select rules not associated to any CWE.")
+      .setExampleValue("12,125," + UNKNOWN_STANDARD);
+
+    action.createParam(PARAM_OWASP_TOP_10)
+      .setDescription("Comma-separated list of OWASP Top 10 lowercase categories. Use '" + UNKNOWN_STANDARD + "' to select rules 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_CWE_MAPPING.keySet());
+
+    action
+      .createParam(PARAM_SONARSOURCE_SECURITY)
+      .setDescription("Comma-separated list of SonarSource report categories.")
+      .setPossibleValues(SecurityStandardHelper.SONARSOURCE_CWE_MAPPING.keySet())
+      .setExampleValue("sql-injection,command-injection");
+
     action
       .createParam(PARAM_LANGUAGES)
       .setDescription("Comma-separated list of languages")
@@ -231,5 +260,4 @@ public class RuleWsSupport {
       .setSince("7.2");
   }
 
-
 }
index ec27029e35fd9388e654d106dd04595e5d1f4e37..bf420fe834773b2ce25b3334c90121efa2768c62 100644 (file)
@@ -33,6 +33,10 @@ public class RulesWsParameters {
   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_CWE = "cwe";
+  public static final String PARAM_OWASP_TOP_10 = "owaspTop10";
+  public static final String PARAM_SANS_TOP_25 = "sansTop25";
+  public static final String PARAM_SONARSOURCE_SECURITY = "sonarsourceSecurity";
   public static final String PARAM_INHERITANCE = "inheritance";
   public static final String PARAM_ACTIVE_SEVERITIES = "active_severities";
   public static final String PARAM_IS_TEMPLATE = "is_template";
index 416b45795ed70ef4aad37dee0f3ea12aaa2ea2d0..6b13e86e3509610e6ee02b084a4aafc8f0ba58da 100644 (file)
@@ -66,18 +66,26 @@ import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
 import static org.sonar.server.es.SearchOptions.MAX_LIMIT;
 import static org.sonar.server.rule.index.RuleIndex.ALL_STATUSES_EXCEPT_REMOVED;
 import static org.sonar.server.rule.index.RuleIndex.FACET_ACTIVE_SEVERITIES;
+import static org.sonar.server.rule.index.RuleIndex.FACET_CWE;
 import static org.sonar.server.rule.index.RuleIndex.FACET_LANGUAGES;
 import static org.sonar.server.rule.index.RuleIndex.FACET_OLD_DEFAULT;
+import static org.sonar.server.rule.index.RuleIndex.FACET_OWASP_TOP_10;
 import static org.sonar.server.rule.index.RuleIndex.FACET_REPOSITORIES;
+import static org.sonar.server.rule.index.RuleIndex.FACET_SANS_TOP_25;
 import static org.sonar.server.rule.index.RuleIndex.FACET_SEVERITIES;
+import static org.sonar.server.rule.index.RuleIndex.FACET_SONARSOURCE_SECURITY;
 import static org.sonar.server.rule.index.RuleIndex.FACET_STATUSES;
 import static org.sonar.server.rule.index.RuleIndex.FACET_TAGS;
 import static org.sonar.server.rule.index.RuleIndex.FACET_TYPES;
 import static org.sonar.server.rule.ws.RulesWsParameters.OPTIONAL_FIELDS;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVE_SEVERITIES;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CWE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_LANGUAGES;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_OWASP_TOP_10;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_REPOSITORIES;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_SANS_TOP_25;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_SEVERITIES;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_SONARSOURCE_SECURITY;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_STATUSES;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_TAGS;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_TYPES;
@@ -95,7 +103,11 @@ public class SearchAction implements RulesWsAction {
     FACET_ACTIVE_SEVERITIES,
     FACET_STATUSES,
     FACET_TYPES,
-    FACET_OLD_DEFAULT};
+    FACET_OLD_DEFAULT,
+    FACET_CWE,
+    FACET_OWASP_TOP_10,
+    FACET_SANS_TOP_25,
+    FACET_SONARSOURCE_SECURITY};
 
   private final RuleQueryFactory ruleQueryFactory;
   private final DbClient dbClient;
@@ -265,6 +277,10 @@ public class SearchAction implements RulesWsAction {
     addMandatoryFacetValues(results, FACET_ACTIVE_SEVERITIES, Severity.ALL);
     addMandatoryFacetValues(results, FACET_TAGS, request.getTags());
     addMandatoryFacetValues(results, FACET_TYPES, RuleType.names());
+    addMandatoryFacetValues(results, FACET_CWE, request.getCwe());
+    addMandatoryFacetValues(results, FACET_OWASP_TOP_10, request.getOwaspTop10());
+    addMandatoryFacetValues(results, FACET_SANS_TOP_25, request.getSansTop25());
+    addMandatoryFacetValues(results, FACET_SONARSOURCE_SECURITY, request.getSonarsourceSecurity());
 
     Common.Facet.Builder facet = Common.Facet.newBuilder();
     Common.FacetValue.Builder value = Common.FacetValue.newBuilder();
@@ -276,6 +292,10 @@ public class SearchAction implements RulesWsAction {
     facetValuesByFacetKey.put(FACET_ACTIVE_SEVERITIES, request.getActiveSeverities());
     facetValuesByFacetKey.put(FACET_TAGS, request.getTags());
     facetValuesByFacetKey.put(FACET_TYPES, request.getTypes());
+    facetValuesByFacetKey.put(FACET_CWE, request.getCwe());
+    facetValuesByFacetKey.put(FACET_OWASP_TOP_10, request.getOwaspTop10());
+    facetValuesByFacetKey.put(FACET_SANS_TOP_25, request.getSansTop25());
+    facetValuesByFacetKey.put(FACET_SONARSOURCE_SECURITY, request.getSonarsourceSecurity());
 
     for (String facetName : context.getFacets()) {
       facet.clear().setProperty(facetName);
@@ -333,7 +353,11 @@ public class SearchAction implements RulesWsAction {
       .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))
+      .setCwe(request.paramAsStrings(PARAM_CWE))
+      .setOwaspTop10(request.paramAsStrings(PARAM_OWASP_TOP_10))
+      .setSansTop25(request.paramAsStrings(PARAM_SANS_TOP_25))
+      .setSonarsourceSecurity(request.paramAsStrings(PARAM_SONARSOURCE_SECURITY));
   }
 
   static class SearchResult {
@@ -416,6 +440,10 @@ public class SearchAction implements RulesWsAction {
     private List<String> statuses;
     private List<String> tags;
     private List<String> types;
+    private List<String> cwe;
+    private List<String> owaspTop10;
+    private List<String> sansTop25;
+    private List<String> sonarsourceSecurity;
 
     private SearchRequest setActiveSeverities(List<String> activeSeverities) {
       this.activeSeverities = activeSeverities;
@@ -507,7 +535,7 @@ public class SearchAction implements RulesWsAction {
       return tags;
     }
 
-    private SearchRequest setTypes(List<String> types) {
+    private SearchRequest setTypes(@Nullable List<String> types) {
       this.types = types;
       return this;
     }
@@ -515,5 +543,41 @@ public class SearchAction implements RulesWsAction {
     private List<String> getTypes() {
       return types;
     }
+
+    public List<String> getCwe() {
+      return cwe;
+    }
+
+    public SearchRequest setCwe(@Nullable List<String> cwe) {
+      this.cwe = cwe;
+      return this;
+    }
+
+    public List<String> getOwaspTop10() {
+      return owaspTop10;
+    }
+
+    public SearchRequest setOwaspTop10(@Nullable List<String> owaspTop10) {
+      this.owaspTop10 = owaspTop10;
+      return this;
+    }
+
+    public List<String> getSansTop25() {
+      return sansTop25;
+    }
+
+    public SearchRequest setSansTop25(@Nullable List<String> sansTop25) {
+      this.sansTop25 = sansTop25;
+      return this;
+    }
+
+    public List<String> getSonarsourceSecurity() {
+      return sonarsourceSecurity;
+    }
+
+    public SearchRequest setSonarsourceSecurity(@Nullable List<String> sonarsourceSecurity) {
+      this.sonarsourceSecurity = sonarsourceSecurity;
+      return this;
+    }
   }
 }
index 143da15a1075df9f7925fb366382616509099f3e..344a8d4d170a01660f75883712dafd3644d70de2 100644 (file)
@@ -38,7 +38,7 @@ import org.sonar.db.rule.RuleDto;
 import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.issue.index.SecurityStandardCategoryStatistics;
-import org.sonar.server.issue.index.SecurityStandardHelper;
+import org.sonar.server.security.SecurityStandardHelper;
 import org.sonar.server.qualityprofile.QPMeasureData;
 import org.sonar.server.qualityprofile.QualityProfile;
 import org.sonar.server.user.UserSession;
@@ -51,11 +51,11 @@ import static java.util.Comparator.comparing;
 import static java.util.stream.Collectors.toList;
 import static org.sonar.api.measures.CoreMetrics.QUALITY_PROFILES_KEY;
 import static org.sonar.api.web.UserRole.USER;
-import static org.sonar.server.issue.index.SecurityStandardHelper.UNKNOWN_STANDARD;
-import static org.sonar.server.issue.index.SecurityStandardHelper.getCwe;
-import static org.sonar.server.issue.index.SecurityStandardHelper.getOwaspTop10;
-import static org.sonar.server.issue.index.SecurityStandardHelper.getSansTop25;
-import static org.sonar.server.issue.index.SecurityStandardHelper.getSonarSourceSecurityCategories;
+import static org.sonar.server.security.SecurityStandardHelper.UNKNOWN_STANDARD;
+import static org.sonar.server.security.SecurityStandardHelper.getCwe;
+import static org.sonar.server.security.SecurityStandardHelper.getOwaspTop10;
+import static org.sonar.server.security.SecurityStandardHelper.getSansTop25;
+import static org.sonar.server.security.SecurityStandardHelper.getSonarSourceSecurityCategories;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25;
index c1d70e74edf5f8e85802bd8f2a1a192ca713c799..bc00cddd508758b43ef630a14ec4c12a2fb44847 100644 (file)
@@ -54,10 +54,10 @@ import static org.sonar.api.resources.Qualifiers.PROJECT;
 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
 import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto;
 import static org.sonar.server.issue.IssueDocTesting.newDoc;
-import static org.sonar.server.issue.index.SecurityStandardHelper.SANS_TOP_25_INSECURE_INTERACTION;
-import static org.sonar.server.issue.index.SecurityStandardHelper.SANS_TOP_25_POROUS_DEFENSES;
-import static org.sonar.server.issue.index.SecurityStandardHelper.SANS_TOP_25_RISKY_RESOURCE;
-import static org.sonar.server.issue.index.SecurityStandardHelper.UNKNOWN_STANDARD;
+import static org.sonar.server.security.SecurityStandardHelper.SANS_TOP_25_INSECURE_INTERACTION;
+import static org.sonar.server.security.SecurityStandardHelper.SANS_TOP_25_POROUS_DEFENSES;
+import static org.sonar.server.security.SecurityStandardHelper.SANS_TOP_25_RISKY_RESOURCE;
+import static org.sonar.server.security.SecurityStandardHelper.UNKNOWN_STANDARD;
 
 public class IssueIndexSecurityReportsTest {
 
index 3619411c221f7c64e3c357d73b12a294a0b223f0..38e1f83d45626d77e0227b8a723a02514147573c 100644 (file)
@@ -102,7 +102,11 @@ public class ActivateRulesActionTest {
       "available_since",
       "activation",
       "severities",
-      "organization");
+      "organization",
+      "cwe",
+      "owaspTop10",
+      "sansTop25",
+      "sonarsourceSecurity");
     WebService.Param targetProfile = definition.param("targetKey");
     assertThat(targetProfile.deprecatedKey()).isEqualTo("profile_key");
     WebService.Param targetSeverity = definition.param("targetSeverity");
index ae9c55e0ad68a8f6cddc30ba9ff5c284c4ae2ca5..4f0311aff961eea332d3b2b5d039755f1bd8c9ad 100644 (file)
@@ -100,7 +100,11 @@ public class DeactivateRulesActionTest {
       "available_since",
       "activation",
       "severities",
-      "organization");
+      "organization",
+      "cwe",
+      "owaspTop10",
+      "sansTop25",
+      "sonarsourceSecurity");
     WebService.Param targetProfile = definition.param("targetKey");
     assertThat(targetProfile.deprecatedKey()).isEqualTo("profile_key");
   }
index 8e1e23dc4357ee03624ccded887979b057f03434..e1272f7aa5e4c6a0b1b2459d2f0b98ef84e633eb 100644 (file)
@@ -136,7 +136,7 @@ public class SearchActionTest {
     assertThat(def.since()).isEqualTo("4.4");
     assertThat(def.isInternal()).isFalse();
     assertThat(def.responseExampleAsString()).isNotEmpty();
-    assertThat(def.params()).hasSize(24);
+    assertThat(def.params()).hasSize(28);
 
     WebService.Param compareToProfile = def.param("compareToProfile");
     assertThat(compareToProfile.since()).isEqualTo("6.5");