From: Sébastien Lesaint Date: Thu, 28 Nov 2019 17:20:26 +0000 (+0100) Subject: SONAR-12717 refactor SecurityStandardHelper into SecurityStandards X-Git-Tag: 8.2.0.32929~219 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=8c4a7402f8623234e80406df350a2d8fe1b8c5da;p=sonarqube.git SONAR-12717 refactor SecurityStandardHelper into SecurityStandards and cleanup responsibility for parsing serialized tags and security standards into table RULES --- diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDefinitionDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDefinitionDto.java index 608eeb9a72f..b16251f1264 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDefinitionDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDefinitionDto.java @@ -19,11 +19,10 @@ */ package org.sonar.db.rule; -import java.util.Arrays; -import java.util.HashSet; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableSet; import java.util.Objects; import java.util.Set; -import java.util.TreeSet; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.apache.commons.lang.StringUtils; @@ -37,6 +36,8 @@ import static com.google.common.base.Preconditions.checkArgument; public class RuleDefinitionDto { + private static final Splitter SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings(); + private Integer id; private String repositoryKey; private String ruleKey; @@ -91,6 +92,26 @@ public class RuleDefinitionDto { private long createdAt; private long updatedAt; + public static Set deserializeTagsString(@Nullable String tags) { + return deserializeStringSet(tags); + } + + public static Set deserializeSecurityStandardsString(@Nullable String securityStandards) { + return deserializeStringSet(securityStandards); + } + + private static Set deserializeStringSet(@Nullable String securityStandards) { + if (securityStandards == null || securityStandards.isEmpty()) { + return ImmutableSet.of(); + } + + return ImmutableSet.copyOf(SPLITTER.split(securityStandards)); + } + + private static String serializeStringSet(@Nullable Set strings) { + return strings == null || strings.isEmpty() ? null : StringUtils.join(strings, ','); + } + public RuleKey getKey() { if (key == null) { key = RuleKey.of(getRepositoryKey(), getRuleKey()); @@ -300,7 +321,7 @@ public class RuleDefinitionDto { } public Set getSystemTags() { - return systemTags == null ? new HashSet<>() : new TreeSet<>(Arrays.asList(StringUtils.split(systemTags, ','))); + return deserializeTagsString(systemTags); } private String getSystemTagsField() { @@ -312,12 +333,12 @@ public class RuleDefinitionDto { } public RuleDefinitionDto setSystemTags(Set tags) { - this.systemTags = tags.isEmpty() ? null : StringUtils.join(tags, ','); + this.systemTags = serializeStringSet(tags); return this; } public Set getSecurityStandards() { - return securityStandards == null ? new HashSet<>() : new TreeSet<>(Arrays.asList(StringUtils.split(securityStandards, ','))); + return deserializeSecurityStandardsString(securityStandards); } private String getSecurityStandardsField() { @@ -329,7 +350,7 @@ public class RuleDefinitionDto { } public RuleDefinitionDto setSecurityStandards(Set standards) { - this.securityStandards = standards.isEmpty() ? null : StringUtils.join(standards, ','); + this.securityStandards = serializeStringSet(standards); return this; } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleForIndexingDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleForIndexingDto.java index add7bd75d32..5a427e86aaa 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleForIndexingDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleForIndexingDto.java @@ -19,8 +19,6 @@ */ package org.sonar.db.rule; -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableSet; import java.util.Set; import javax.annotation.CheckForNull; import org.sonar.api.rule.RuleKey; @@ -49,9 +47,6 @@ public class RuleForIndexingDto { private long createdAt; private long updatedAt; - private static final Splitter TAGS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings(); - private static final Splitter SECURITY_STANDARDS_SPLITTER = TAGS_SPLITTER; - public Integer getId() { return id; } @@ -88,12 +83,12 @@ public class RuleForIndexingDto { return isTemplate; } - public String getSystemTags() { - return systemTags; + public Set getSystemTags() { + return RuleDefinitionDto.deserializeTagsString(systemTags); } - public String getSecurityStandards() { - return securityStandards; + public Set getSecurityStandards() { + return RuleDefinitionDto.deserializeSecurityStandardsString(securityStandards); } public String getTemplateRuleKey() { @@ -140,12 +135,4 @@ public class RuleForIndexingDto { public RuleKey getRuleKey() { return RuleKey.of(repository, pluginRuleKey); } - - public Set getSystemTagsAsSet() { - return ImmutableSet.copyOf(TAGS_SPLITTER.split(systemTags == null ? "" : systemTags)); - } - - public Set getSecurityStandardsAsSet() { - return ImmutableSet.copyOf(SECURITY_STANDARDS_SPLITTER.split(securityStandards == null ? "" : securityStandards)); - } } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleDaoTest.java index da49894aecd..5fc68e9ab2d 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleDaoTest.java @@ -957,8 +957,8 @@ public class RuleDaoTest { assertThat(firstRule.getStatus()).isEqualTo(r1.getStatus()); assertThat(firstRule.isExternal()).isFalse(); assertThat(firstRule.isTemplate()).isEqualTo(r1.isTemplate()); - assertThat(firstRule.getSystemTagsAsSet()).isEqualTo(r1.getSystemTags()); - assertThat(firstRule.getSecurityStandardsAsSet()).isEqualTo(r1.getSecurityStandards()); + assertThat(firstRule.getSystemTags()).isEqualTo(r1.getSystemTags()); + assertThat(firstRule.getSecurityStandards()).isEqualTo(r1.getSecurityStandards()); assertThat(firstRule.getTemplateRuleKey()).isNull(); assertThat(firstRule.getTemplateRepository()).isNull(); assertThat(firstRule.getInternalKey()).isEqualTo(r1.getConfigKey()); @@ -1036,8 +1036,8 @@ public class RuleDaoTest { assertThat(firstRule.getSeverityAsString()).isEqualTo(SeverityUtil.getSeverityFromOrdinal(r1.getSeverity())); assertThat(firstRule.getStatus()).isEqualTo(r1.getStatus()); assertThat(firstRule.isTemplate()).isEqualTo(r1.isTemplate()); - assertThat(firstRule.getSystemTagsAsSet()).isEqualTo(r1.getSystemTags()); - assertThat(firstRule.getSecurityStandardsAsSet()).isEqualTo(r1.getSecurityStandards()); + assertThat(firstRule.getSystemTags()).isEqualTo(r1.getSystemTags()); + assertThat(firstRule.getSecurityStandards()).isEqualTo(r1.getSecurityStandards()); assertThat(firstRule.getInternalKey()).isEqualTo(r1.getConfigKey()); assertThat(firstRule.getLanguage()).isEqualTo(r1.getLanguage()); assertThat(firstRule.getType()).isEqualTo(r1.getType()); 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 160247904de..862c468bf16 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 @@ -27,7 +27,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; import java.util.HashMap; -import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; import javax.annotation.CheckForNull; @@ -39,15 +38,13 @@ import org.sonar.db.DatabaseUtils; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.ResultSetIterator; +import org.sonar.server.security.SecurityStandards; 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.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; +import static org.sonar.db.rule.RuleDefinitionDto.deserializeSecurityStandardsString; +import static org.sonar.server.security.SecurityStandards.fromSecurityStandards; /** * Scrolls over table ISSUES and reads documents to populate @@ -231,14 +228,12 @@ class IssueIteratorForSingleChunk implements IssueIterator { String tags = rs.getString(21); doc.setTags(IssueIteratorForSingleChunk.TAGS_SPLITTER.splitToList(tags == null ? "" : tags)); doc.setType(RuleType.valueOf(rs.getInt(22))); - String securityStandards = rs.getString(23); - List standards = getSecurityStandards(securityStandards); - doc.setOwaspTop10(getOwaspTop10(standards)); - List cwe = getCwe(standards); - doc.setCwe(cwe); - doc.setSansTop25(getSansTop25(cwe)); - doc.setSonarSourceSecurityCategories(getSonarSourceSecurityCategories(cwe)); + SecurityStandards securityStandards = fromSecurityStandards(deserializeSecurityStandardsString(rs.getString(23))); + doc.setOwaspTop10(securityStandards.getOwaspTop10()); + doc.setCwe(securityStandards.getCwe()); + doc.setSansTop25(securityStandards.getSansTop25()); + doc.setSonarSourceSecurityCategories(securityStandards.getSq()); return doc; } diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleDoc.java b/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleDoc.java index ff0c4a3a59a..cf2512fcfeb 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleDoc.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleDoc.java @@ -35,7 +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 org.sonar.server.security.SecurityStandards; import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE; @@ -268,7 +268,7 @@ public class RuleDoc extends BaseDoc { } public static RuleDoc of(RuleForIndexingDto dto) { - Collection cwe = SecurityStandardHelper.getCwe(dto.getSecurityStandardsAsSet()); + SecurityStandards securityStandards = SecurityStandards.fromSecurityStandards(dto.getSecurityStandards()); RuleDoc ruleDoc = new RuleDoc() .setId(dto.getId()) .setKey(dto.getRuleKey().toString()) @@ -277,10 +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)) + .setCwe(securityStandards.getCwe()) + .setOwaspTop10(securityStandards.getOwaspTop10()) + .setSansTop25(securityStandards.getSansTop25()) + .setSonarSourceSecurityCategories(securityStandards.getSq()) .setName(dto.getName()) .setRuleKey(dto.getPluginRuleKey()) .setSeverity(dto.getSeverityAsString()) diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleExtensionDoc.java b/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleExtensionDoc.java index b6982841a4f..8894f8f15e2 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleExtensionDoc.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleExtensionDoc.java @@ -81,7 +81,7 @@ public class RuleExtensionDoc extends BaseDoc { return new RuleExtensionDoc() .setRuleId(rule.getId()) .setScope(RuleExtensionScope.system()) - .setTags(rule.getSystemTagsAsSet()); + .setTags(rule.getSystemTags()); } public static RuleExtensionDoc of(RuleExtensionForIndexingDto rule) { 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 deleted file mode 100644 index acfbb9f8021..00000000000 --- a/server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityStandardHelper.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Ordering; -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 INSECURE_CWE = new HashSet<>(asList("89", "78", "79", "434", "352", "601")); - private static final Set RISKY_CWE = new HashSet<>(asList("120", "22", "494", "829", "676", "131", "134", "190")); - private static final Set POROUS_CWE = new HashSet<>(asList("306", "862", "798", "311", "807", "250", "863", "732", "327", "307", "759")); - public static final Map> SANS_TOP_25_CWE_MAPPING = ImmutableMap.of( - SANS_TOP_25_INSECURE_INTERACTION, INSECURE_CWE, - SANS_TOP_25_RISKY_RESOURCE, RISKY_CWE, - SANS_TOP_25_POROUS_DEFENSES, POROUS_CWE); - - public static final Map> SONARSOURCE_CWE_MAPPING = ImmutableMap.>builder() - .put("sql-injection", ImmutableSet.of("89", "564")) - .put("command-injection", ImmutableSet.of("77", "78", "88", "214")) - .put("path-traversal-injection", ImmutableSet.of("22")) - .put("ldap-injection", ImmutableSet.of("90")) - .put("xpath-injection", ImmutableSet.of("643")) - .put("rce", ImmutableSet.of("94", "95")) - .put("dos", ImmutableSet.of("400", "624")) - .put("ssrf", ImmutableSet.of("918")) - .put("csrf", ImmutableSet.of("352")) - .put("xss", ImmutableSet.of("79", "80", "81", "82", "83", "84", "85", "86", "87")) - .put("log-injection", ImmutableSet.of("117")) - .put("http-response-splitting", ImmutableSet.of("113")) - .put("open-redirect", ImmutableSet.of("601")) - .put("xxe", ImmutableSet.of("611", "827")) - .put("object-injection", ImmutableSet.of("134", "470", "502")) - .put("weak-cryptography", ImmutableSet.of("295", "297", "321", "322", "323", "324", "325", "326", "327", "328", "330", "780")) - .put("auth", ImmutableSet.of("798", "640", "620", "549", "522", "521", "263", "262", "261", "259", "284")) - .put("insecure-conf", ImmutableSet.of("102", "215", "311", "315", "346", "614", "489", "942")) - .put("file-manipulation", ImmutableSet.of("97", "73")) - .build(); - public static final String SONARSOURCE_OTHER_CWES_CATEGORY = "others"; - public static final Ordering SONARSOURCE_CATEGORY_ORDERING = Ordering.explicit( - ImmutableList.builder().addAll(SONARSOURCE_CWE_MAPPING.keySet()).add(SONARSOURCE_OTHER_CWES_CATEGORY).build()); - - private static final Splitter SECURITY_STANDARDS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings(); - - private SecurityStandardHelper() { - // Utility class - } - - public static List getSecurityStandards(@Nullable String securityStandards) { - return securityStandards == null ? emptyList() : SECURITY_STANDARDS_SPLITTER.splitToList(securityStandards); - } - - public static List getSansTop25(Collection 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 getSonarSourceSecurityCategories(Collection cwe) { - List result = SONARSOURCE_CWE_MAPPING - .keySet() - .stream() - .filter(k -> cwe.stream().anyMatch(SONARSOURCE_CWE_MAPPING.get(k)::contains)) - .collect(toList()); - return result.isEmpty() ? singletonList(SONARSOURCE_OTHER_CWES_CATEGORY) : result; - } - - public static List getOwaspTop10(Collection securityStandards) { - return securityStandards.stream() - .filter(s -> s.startsWith(OWASP_TOP10_PREFIX)) - .map(s -> s.substring(OWASP_TOP10_PREFIX.length())) - .collect(toList()); - } - - public static List getCwe(Collection securityStandards) { - List 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 getSansTop25(String securityStandards) { - return getSansTop25(getCwe(getSecurityStandards(securityStandards))); - } - - public static List getSonarSourceSecurityCategories(String securityStandards) { - return getSonarSourceSecurityCategories(getCwe(getSecurityStandards(securityStandards))); - } - - public static List getOwaspTop10(String securityStandards) { - return getOwaspTop10(getSecurityStandards(securityStandards)); - } - - public static List getCwe(String securityStandards) { - return getCwe(getSecurityStandards(securityStandards)); - } -} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityStandards.java b/server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityStandards.java new file mode 100644 index 00000000000..9a0e7852754 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityStandards.java @@ -0,0 +1,158 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Ordering; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import javax.annotation.concurrent.Immutable; +import org.sonar.core.util.stream.MoreCollectors; + +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; + +@Immutable +public final class SecurityStandards { + + 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 INSECURE_CWE = new HashSet<>(asList("89", "78", "79", "434", "352", "601")); + private static final Set RISKY_CWE = new HashSet<>(asList("120", "22", "494", "829", "676", "131", "134", "190")); + private static final Set POROUS_CWE = new HashSet<>(asList("306", "862", "798", "311", "807", "250", "863", "732", "327", "307", "759")); + public static final Map> CWES_BY_SANS_TOP_25 = ImmutableMap.of( + SANS_TOP_25_INSECURE_INTERACTION, INSECURE_CWE, + SANS_TOP_25_RISKY_RESOURCE, RISKY_CWE, + SANS_TOP_25_POROUS_DEFENSES, POROUS_CWE); + + public static final Map> CWES_BY_SQ_CATEGORY = ImmutableMap.>builder() + .put("sql-injection", ImmutableSet.of("89", "564")) + .put("command-injection", ImmutableSet.of("77", "78", "88", "214")) + .put("path-traversal-injection", ImmutableSet.of("22")) + .put("ldap-injection", ImmutableSet.of("90")) + .put("xpath-injection", ImmutableSet.of("643")) + .put("rce", ImmutableSet.of("94", "95")) + .put("dos", ImmutableSet.of("400", "624")) + .put("ssrf", ImmutableSet.of("918")) + .put("csrf", ImmutableSet.of("352")) + .put("xss", ImmutableSet.of("79", "80", "81", "82", "83", "84", "85", "86", "87")) + .put("log-injection", ImmutableSet.of("117")) + .put("http-response-splitting", ImmutableSet.of("113")) + .put("open-redirect", ImmutableSet.of("601")) + .put("xxe", ImmutableSet.of("611", "827")) + .put("object-injection", ImmutableSet.of("134", "470", "502")) + .put("weak-cryptography", ImmutableSet.of("295", "297", "321", "322", "323", "324", "325", "326", "327", "328", "330", "780")) + .put("auth", ImmutableSet.of("798", "640", "620", "549", "522", "521", "263", "262", "261", "259", "284")) + .put("insecure-conf", ImmutableSet.of("102", "215", "311", "315", "346", "614", "489", "942")) + .put("file-manipulation", ImmutableSet.of("97", "73")) + .build(); + public static final String SQ_OTHER_CATEGORY = "others"; + public static final Set SQ_CATEGORIES = ImmutableSet.builder().addAll(CWES_BY_SQ_CATEGORY.keySet()).add(SQ_OTHER_CATEGORY).build(); + public static final Ordering SQ_CATEGORIES_ORDERING = Ordering.explicit( + ImmutableList.builder().addAll(CWES_BY_SQ_CATEGORY.keySet()).add(SQ_OTHER_CATEGORY).build()); + + private final Set standards; + private final Set cwe; + private final Set owaspTop10; + private final Set sansTop25; + private final Set sq; + + private SecurityStandards(Set standards, Set cwe, Set owaspTop10, Set sansTop25, Set sq) { + this.standards = standards; + this.cwe = cwe; + this.owaspTop10 = owaspTop10; + this.sansTop25 = sansTop25; + this.sq = sq; + } + + public Set getStandards() { + return standards; + } + + public Set getCwe() { + return cwe; + } + + public Set getOwaspTop10() { + return owaspTop10; + } + + public Set getSansTop25() { + return sansTop25; + } + + public Set getSq() { + return sq; + } + + public static SecurityStandards fromSecurityStandards(Set securityStandards) { + Set standards = securityStandards.stream() + .filter(Objects::nonNull) + .collect(MoreCollectors.toSet()); + Set owaspTop10 = toOwaspTop10(standards); + Set cwe = toCwe(standards); + Set sansTop25 = toSansTop25(cwe); + Set sq = toSonarSourceSecurityCategories(cwe); + return new SecurityStandards(standards, cwe, owaspTop10, sansTop25, sq); + } + + private static Set toOwaspTop10(Set securityStandards) { + return securityStandards.stream() + .filter(s -> s.startsWith(OWASP_TOP10_PREFIX)) + .map(s -> s.substring(OWASP_TOP10_PREFIX.length())) + .collect(MoreCollectors.toSet()); + } + + private static Set toCwe(Collection securityStandards) { + Set result = securityStandards.stream() + .filter(s -> s.startsWith(CWE_PREFIX)) + .map(s -> s.substring(CWE_PREFIX.length())) + .collect(MoreCollectors.toSet()); + return result.isEmpty() ? singleton(UNKNOWN_STANDARD) : result; + } + + private static Set toSansTop25(Collection cwe) { + return CWES_BY_SANS_TOP_25 + .keySet() + .stream() + .filter(k -> cwe.stream().anyMatch(CWES_BY_SANS_TOP_25.get(k)::contains)) + .collect(MoreCollectors.toSet()); + } + + private static Set toSonarSourceSecurityCategories(Collection cwe) { + Set result = CWES_BY_SQ_CATEGORY + .keySet() + .stream() + .filter(k -> cwe.stream().anyMatch(CWES_BY_SQ_CATEGORY.get(k)::contains)) + .collect(MoreCollectors.toSet()); + return result.isEmpty() ? singleton(SQ_OTHER_CATEGORY) : result; + } +} 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 61aa2fb4ff3..c78c86cd4c0 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 @@ -48,6 +48,7 @@ import org.sonar.server.es.IndexingResult; import org.sonar.server.es.ProjectIndexer; import org.sonar.server.permission.index.AuthorizationScope; import org.sonar.server.permission.index.IndexPermissions; +import org.sonar.server.security.SecurityStandards; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -57,10 +58,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.security.SecurityStandardHelper.SANS_TOP_25_POROUS_DEFENSES; -import static org.sonar.server.security.SecurityStandardHelper.SONARSOURCE_OTHER_CWES_CATEGORY; -import static org.sonar.server.security.SecurityStandardHelper.UNKNOWN_STANDARD; import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE_AUTHORIZATION; +import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_POROUS_DEFENSES; public class IssueIndexerTest { @@ -136,10 +135,10 @@ 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.getCwe()).containsExactlyInAnyOrder(SecurityStandards.UNKNOWN_STANDARD); assertThat(doc.getOwaspTop10()).isEmpty(); assertThat(doc.getSansTop25()).isEmpty(); - assertThat(doc.getSonarSourceSecurityCategories()).containsExactlyInAnyOrder(SONARSOURCE_OTHER_CWES_CATEGORY); + assertThat(doc.getSonarSourceSecurityCategories()).containsOnly(SecurityStandards.SQ_OTHER_CATEGORY); } @Test diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java index 312948b0d35..d4280569e4e 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java @@ -86,8 +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; +import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_INSECURE_INTERACTION; +import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_RISKY_RESOURCE; public class RuleIndexTest { diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java index 221fbe817a8..7dba189141a 100644 --- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java +++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java @@ -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.SecurityStandards; import org.sonar.server.user.UserSession; import org.sonar.server.view.index.ViewIndexDefinition; @@ -152,12 +153,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.security.SecurityStandardHelper.SANS_TOP_25_CWE_MAPPING; -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.SONARSOURCE_OTHER_CWES_CATEGORY; +import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_INSECURE_INTERACTION; +import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_POROUS_DEFENSES; +import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_RISKY_RESOURCE; +import static org.sonar.server.security.SecurityStandards.SQ_CATEGORIES; 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; @@ -874,15 +873,14 @@ public class IssueIndex { public List getSansTop25Report(String projectUuid, boolean isViewOrApp, boolean includeCwe) { SearchRequestBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp); Stream.of(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES) - .forEach(sansCategory -> request.addAggregation(createAggregation(FIELD_ISSUE_SANS_TOP_25, sansCategory, includeCwe, Optional.of(SANS_TOP_25_CWE_MAPPING)))); + .forEach(sansCategory -> request.addAggregation(createAggregation(FIELD_ISSUE_SANS_TOP_25, sansCategory, includeCwe, Optional.of(SecurityStandards.CWES_BY_SANS_TOP_25)))); return processSecurityReportSearchResults(request, includeCwe); } public List getSonarSourceReport(String projectUuid, boolean isViewOrApp, boolean includeCwe) { SearchRequestBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp); - Stream.concat(SONARSOURCE_CWE_MAPPING.keySet().stream(), Stream.of(SONARSOURCE_OTHER_CWES_CATEGORY)) - .forEach(sonarsourceCategory -> request.addAggregation( - createAggregation(FIELD_ISSUE_SONARSOURCE_SECURITY, sonarsourceCategory, includeCwe, Optional.of(SONARSOURCE_CWE_MAPPING)))); + SQ_CATEGORIES.forEach(sonarsourceCategory -> request.addAggregation( + createAggregation(FIELD_ISSUE_SONARSOURCE_SECURITY, sonarsourceCategory, includeCwe, Optional.of(SecurityStandards.CWES_BY_SQ_CATEGORY)))); return processSecurityReportSearchResults(request, includeCwe); } diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java index 1b75d334230..47915fff2cf 100644 --- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java +++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java @@ -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.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.security.SecurityStandards.SANS_TOP_25_INSECURE_INTERACTION; +import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_POROUS_DEFENSES; +import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_RISKY_RESOURCE; +import static org.sonar.server.security.SecurityStandards.UNKNOWN_STANDARD; public class IssueIndexSecurityReportsTest { diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java index f06ac22cdd4..ec348953451 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java @@ -54,6 +54,7 @@ import org.sonar.server.issue.SearchRequest; import org.sonar.server.issue.index.IssueIndex; import org.sonar.server.issue.index.IssueQuery; import org.sonar.server.issue.index.IssueQueryFactory; +import org.sonar.server.security.SecurityStandards; import org.sonar.server.user.UserSession; import org.sonarqube.ws.Issues.SearchWsResponse; @@ -84,12 +85,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.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.SONARSOURCE_OTHER_CWES_CATEGORY; -import static org.sonar.server.security.SecurityStandardHelper.UNKNOWN_STANDARD; +import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_INSECURE_INTERACTION; +import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_POROUS_DEFENSES; +import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_RISKY_RESOURCE; +import static org.sonar.server.security.SecurityStandards.SQ_OTHER_CATEGORY; +import static org.sonar.server.security.SecurityStandards.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; @@ -273,10 +273,10 @@ public class SearchAction implements IssuesWsAction, Startable { .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_SONARSOURCE_SECURITY) - .setDescription("Comma-separated list of SonarSource security categories. Use '" + SONARSOURCE_OTHER_CWES_CATEGORY + "' to select issues not associated" + + .setDescription("Comma-separated list of SonarSource security categories. Use '" + SQ_OTHER_CATEGORY + "' to select issues not associated" + " with any category") .setSince("7.8") - .setPossibleValues(ImmutableList.builder().addAll(SONARSOURCE_CWE_MAPPING.keySet()).add(SONARSOURCE_OTHER_CWES_CATEGORY).build()); + .setPossibleValues(SecurityStandards.SQ_CATEGORIES); action.createParam(DEPRECATED_PARAM_AUTHORS) .setDeprecatedSince("7.7") .setDescription("This parameter is deprecated, please use '%s' instead", PARAM_AUTHOR) diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleWsSupport.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleWsSupport.java index cd1bb3c6f3a..bd44b41d8f0 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleWsSupport.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleWsSupport.java @@ -19,7 +19,6 @@ */ package org.sonar.server.rule.ws; -import com.google.common.collect.ImmutableList; import java.util.List; import java.util.Map; import java.util.Objects; @@ -39,6 +38,7 @@ import org.sonar.db.user.UserDto; import org.sonar.server.organization.DefaultOrganizationProvider; import org.sonar.server.qualityprofile.ActiveRuleInheritance; import org.sonar.server.rule.index.RuleIndexDefinition; +import org.sonar.server.security.SecurityStandards; import org.sonar.server.user.UserSession; import static org.sonar.api.server.ws.WebService.Param.ASCENDING; @@ -50,6 +50,7 @@ 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.exceptions.NotFoundException.checkFoundWithOptional; 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; @@ -71,11 +72,7 @@ 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; import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_TYPES; -import static org.sonar.server.security.SecurityStandardHelper.SANS_TOP_25_CWE_MAPPING; -import static org.sonar.server.security.SecurityStandardHelper.SONARSOURCE_CWE_MAPPING; -import static org.sonar.server.security.SecurityStandardHelper.SONARSOURCE_OTHER_CWES_CATEGORY; -import static org.sonar.server.security.SecurityStandardHelper.UNKNOWN_STANDARD; -import static org.sonar.server.exceptions.NotFoundException.checkFoundWithOptional; +import static org.sonar.server.security.SecurityStandards.SQ_OTHER_CATEGORY; @ServerSide public class RuleWsSupport { @@ -141,8 +138,8 @@ public class RuleWsSupport { 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); + .setDescription("Comma-separated list of CWE identifiers. Use '" + SecurityStandards.UNKNOWN_STANDARD + "' to select rules not associated to any CWE.") + .setExampleValue("12,125," + SecurityStandards.UNKNOWN_STANDARD); action.createParam(PARAM_OWASP_TOP_10) .setDescription("Comma-separated list of OWASP Top 10 lowercase categories.") @@ -152,14 +149,14 @@ public class RuleWsSupport { 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()); + .setPossibleValues(SecurityStandards.CWES_BY_SANS_TOP_25.keySet()); action .createParam(PARAM_SONARSOURCE_SECURITY) - .setDescription("Comma-separated list of SonarSource security categories. Use '" + SONARSOURCE_OTHER_CWES_CATEGORY + "' to select rules not associated" + + .setDescription("Comma-separated list of SonarSource security categories. Use '" + SQ_OTHER_CATEGORY + "' to select rules not associated" + " with any category") .setSince("7.8") - .setPossibleValues(ImmutableList.builder().addAll(SONARSOURCE_CWE_MAPPING.keySet()).add(SONARSOURCE_OTHER_CWES_CATEGORY).build()) + .setPossibleValues(SecurityStandards.SQ_CATEGORIES) .setExampleValue("sql-injection,command-injection,others"); action