if it's not the case, only one is taken into account a WARN log is displayed at startup to indicate rules wich do not complytags/8.2.0.32929
@@ -30,6 +30,7 @@ import org.sonar.api.rules.RuleType; | |||
import org.sonar.api.utils.Duration; | |||
import org.sonar.server.es.BaseDoc; | |||
import org.sonar.server.permission.index.AuthorizationDoc; | |||
import org.sonar.server.security.SecurityStandards; | |||
import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE; | |||
@@ -315,12 +316,13 @@ public class IssueDoc extends BaseDoc { | |||
} | |||
@CheckForNull | |||
public Collection<String> getSonarSourceSecurityCategories() { | |||
return getNullableField(IssueIndexDefinition.FIELD_ISSUE_SONARSOURCE_SECURITY); | |||
public SecurityStandards.SQCategory getSonarSourceSecurityCategory() { | |||
String key = getNullableField(IssueIndexDefinition.FIELD_ISSUE_SONARSOURCE_SECURITY); | |||
return SecurityStandards.SQCategory.fromKey(key).orElse(null); | |||
} | |||
public IssueDoc setSonarSourceSecurityCategories(@Nullable Collection<String> c) { | |||
setField(IssueIndexDefinition.FIELD_ISSUE_SONARSOURCE_SECURITY, c); | |||
public IssueDoc setSonarSourceSecurityCategory(@Nullable SecurityStandards.SQCategory c) { | |||
setField(IssueIndexDefinition.FIELD_ISSUE_SONARSOURCE_SECURITY, c == null ? null : c.getKey()); | |||
return this; | |||
} | |||
} |
@@ -39,10 +39,8 @@ import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.ResultSetIterator; | |||
import org.sonar.server.security.SecurityStandards; | |||
import org.sonar.server.security.SecurityStandards.SQCategory; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
import static java.util.stream.Collectors.toList; | |||
import static org.sonar.api.utils.DateUtils.longToDate; | |||
import static org.sonar.db.DatabaseUtils.getLong; | |||
import static org.sonar.db.rule.RuleDefinitionDto.deserializeSecurityStandardsString; | |||
@@ -235,7 +233,7 @@ class IssueIteratorForSingleChunk implements IssueIterator { | |||
doc.setOwaspTop10(securityStandards.getOwaspTop10()); | |||
doc.setCwe(securityStandards.getCwe()); | |||
doc.setSansTop25(securityStandards.getSansTop25()); | |||
doc.setSonarSourceSecurityCategories(securityStandards.getSq().stream().map(SQCategory::getKey).collect(toList())); | |||
doc.setSonarSourceSecurityCategory(securityStandards.getSqCategory()); | |||
return doc; | |||
} | |||
@@ -38,7 +38,6 @@ import org.sonar.server.es.BaseDoc; | |||
import org.sonar.server.security.SecurityStandards; | |||
import org.sonar.server.security.SecurityStandards.SQCategory; | |||
import static java.util.stream.Collectors.toList; | |||
import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE; | |||
@@ -188,12 +187,13 @@ public class RuleDoc extends BaseDoc { | |||
} | |||
@CheckForNull | |||
public Collection<String> getSonarSourceSecurityCategories() { | |||
return getNullableField(RuleIndexDefinition.FIELD_RULE_SONARSOURCE_SECURITY); | |||
public SQCategory getSonarSourceSecurityCategory() { | |||
String key = getNullableField(RuleIndexDefinition.FIELD_RULE_SONARSOURCE_SECURITY); | |||
return SQCategory.fromKey(key).orElse(null); | |||
} | |||
public RuleDoc setSonarSourceSecurityCategories(@Nullable Collection<String> c) { | |||
setField(RuleIndexDefinition.FIELD_RULE_SONARSOURCE_SECURITY, c); | |||
public RuleDoc setSonarSourceSecurityCategory(@Nullable SQCategory sqCategory) { | |||
setField(RuleIndexDefinition.FIELD_RULE_SONARSOURCE_SECURITY, sqCategory == null ? null : sqCategory.getKey()); | |||
return this; | |||
} | |||
@@ -269,8 +269,7 @@ public class RuleDoc extends BaseDoc { | |||
return ReflectionToStringBuilder.toString(this); | |||
} | |||
public static RuleDoc of(RuleForIndexingDto dto) { | |||
SecurityStandards securityStandards = SecurityStandards.fromSecurityStandards(dto.getSecurityStandards()); | |||
public static RuleDoc of(RuleForIndexingDto dto, SecurityStandards securityStandards) { | |||
RuleDoc ruleDoc = new RuleDoc() | |||
.setId(dto.getId()) | |||
.setKey(dto.getRuleKey().toString()) | |||
@@ -282,7 +281,7 @@ public class RuleDoc extends BaseDoc { | |||
.setCwe(securityStandards.getCwe()) | |||
.setOwaspTop10(securityStandards.getOwaspTop10()) | |||
.setSansTop25(securityStandards.getSansTop25()) | |||
.setSonarSourceSecurityCategories(securityStandards.getSq().stream().map(SQCategory::getKey).collect(toList())) | |||
.setSonarSourceSecurityCategory(securityStandards.getSqCategory()) | |||
.setName(dto.getName()) | |||
.setRuleKey(dto.getPluginRuleKey()) | |||
.setSeverity(dto.getSeverityAsString()) |
@@ -25,12 +25,15 @@ import java.util.Collection; | |||
import java.util.List; | |||
import java.util.Optional; | |||
import java.util.Set; | |||
import java.util.stream.Stream; | |||
import org.sonar.api.utils.log.Loggers; | |||
import org.sonar.core.util.stream.MoreCollectors; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.es.EsQueueDto; | |||
import org.sonar.db.es.RuleExtensionId; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.rule.RuleForIndexingDto; | |||
import org.sonar.server.es.BulkIndexer; | |||
import org.sonar.server.es.BulkIndexer.Size; | |||
import org.sonar.server.es.EsClient; | |||
@@ -39,16 +42,19 @@ import org.sonar.server.es.IndexingListener; | |||
import org.sonar.server.es.IndexingResult; | |||
import org.sonar.server.es.OneToOneResilientIndexingListener; | |||
import org.sonar.server.es.ResilientIndexer; | |||
import org.sonar.server.security.SecurityStandards; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
import static java.util.Arrays.asList; | |||
import static java.util.Collections.singletonList; | |||
import static java.util.stream.Collectors.joining; | |||
import static java.util.stream.Stream.concat; | |||
import static org.sonar.core.util.stream.MoreCollectors.toHashSet; | |||
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.SecurityStandards.SQ_CATEGORY_KEYS_ORDERING; | |||
public class RuleIndexer implements ResilientIndexer { | |||
private final EsClient esClient; | |||
private final DbClient dbClient; | |||
@@ -71,7 +77,7 @@ public class RuleIndexer implements ResilientIndexer { | |||
// index all definitions and system extensions | |||
if (uninitializedIndexTypes.contains(TYPE_RULE)) { | |||
dbClient.ruleDao().scrollIndexingRules(dbSession, dto -> { | |||
bulk.add(RuleDoc.of(dto).toIndexRequest()); | |||
bulk.add(ruleDocOf(dto).toIndexRequest()); | |||
bulk.add(RuleExtensionDoc.of(dto).toIndexRequest()); | |||
}); | |||
} | |||
@@ -142,7 +148,7 @@ public class RuleIndexer implements ResilientIndexer { | |||
dbClient.ruleDao().scrollIndexingRulesByKeys(dbSession, ruleIds, | |||
r -> { | |||
bulkIndexer.add(RuleDoc.of(r).toIndexRequest()); | |||
bulkIndexer.add(ruleDocOf(r).toIndexRequest()); | |||
bulkIndexer.add(RuleExtensionDoc.of(r).toIndexRequest()); | |||
ruleIds.remove(r.getId()); | |||
}); | |||
@@ -186,6 +192,21 @@ public class RuleIndexer implements ResilientIndexer { | |||
return Optional.of(bulkIndexer.stop()); | |||
} | |||
private RuleDoc ruleDocOf(RuleForIndexingDto dto) { | |||
SecurityStandards securityStandards = SecurityStandards.fromSecurityStandards(dto.getSecurityStandards()); | |||
if (!securityStandards.getIgnoredSQCategories().isEmpty()) { | |||
Loggers.get(RuleIndexer.class).warn( | |||
"Rule {} with CWEs '{}' maps to multiple SQ Security Categories: {}", | |||
dto.getRuleKey(), | |||
String.join(", ", securityStandards.getCwe()), | |||
concat(Stream.of(securityStandards.getSqCategory()), securityStandards.getIgnoredSQCategories().stream()) | |||
.map(SecurityStandards.SQCategory::getKey) | |||
.sorted(SQ_CATEGORY_KEYS_ORDERING) | |||
.collect(joining(", "))); | |||
} | |||
return RuleDoc.of(dto, securityStandards); | |||
} | |||
private BulkIndexer createBulkIndexer(Size bulkSize, IndexingListener listener) { | |||
return new BulkIndexer(esClient, TYPE_RULE, bulkSize, listener); | |||
} |
@@ -22,18 +22,24 @@ package org.sonar.server.security; | |||
import com.google.common.collect.ImmutableMap; | |||
import com.google.common.collect.ImmutableSet; | |||
import com.google.common.collect.Ordering; | |||
import java.util.Arrays; | |||
import java.util.Collection; | |||
import java.util.HashSet; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Objects; | |||
import java.util.Optional; | |||
import java.util.Set; | |||
import java.util.stream.Collectors; | |||
import javax.annotation.Nullable; | |||
import javax.annotation.concurrent.Immutable; | |||
import org.sonar.core.util.stream.MoreCollectors; | |||
import static java.util.Arrays.asList; | |||
import static java.util.Arrays.stream; | |||
import static java.util.Collections.singleton; | |||
import static java.util.Collections.singletonList; | |||
import static org.sonar.core.util.stream.MoreCollectors.toList; | |||
import static org.sonar.core.util.stream.MoreCollectors.toSet; | |||
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; | |||
import static org.sonar.server.security.SecurityStandards.VulnerabilityProbability.HIGH; | |||
import static org.sonar.server.security.SecurityStandards.VulnerabilityProbability.LOW; | |||
import static org.sonar.server.security.SecurityStandards.VulnerabilityProbability.MEDIUM; | |||
@@ -85,6 +91,7 @@ public final class SecurityStandards { | |||
FILE_MANIPULATION("file-manipulation", LOW), | |||
OTHERS("others", LOW); | |||
private static final Map<String, SQCategory> SQ_CATEGORY_BY_KEY = stream(values()).collect(uniqueIndex(SQCategory::getKey)); | |||
private final String key; | |||
private final VulnerabilityProbability vulnerability; | |||
@@ -100,6 +107,10 @@ public final class SecurityStandards { | |||
public VulnerabilityProbability getVulnerability() { | |||
return vulnerability; | |||
} | |||
public static Optional<SQCategory> fromKey(@Nullable String key) { | |||
return Optional.ofNullable(key).map(SQ_CATEGORY_BY_KEY::get); | |||
} | |||
} | |||
public static final Map<SQCategory, Set<String>> CWES_BY_SQ_CATEGORY = ImmutableMap.<SQCategory, Set<String>>builder() | |||
@@ -123,21 +134,23 @@ public final class SecurityStandards { | |||
.put(SQCategory.INSECURE_CONF, ImmutableSet.of("102", "215", "311", "315", "346", "614", "489", "942")) | |||
.put(SQCategory.FILE_MANIPULATION, ImmutableSet.of("97", "73")) | |||
.build(); | |||
public static final Ordering<SQCategory> SQ_CATEGORY_ORDERING = Ordering.explicit(Arrays.stream(SQCategory.values()).collect(Collectors.toList())); | |||
public static final Ordering<String> SQ_CATEGORY_KEYS_ORDERING = Ordering.explicit(Arrays.stream(SQCategory.values()).map(SQCategory::getKey).collect(Collectors.toList())); | |||
private static final Ordering<SQCategory> SQ_CATEGORY_ORDERING = Ordering.explicit(stream(SQCategory.values()).collect(Collectors.toList())); | |||
public static final Ordering<String> SQ_CATEGORY_KEYS_ORDERING = Ordering.explicit(stream(SQCategory.values()).map(SQCategory::getKey).collect(Collectors.toList())); | |||
private final Set<String> standards; | |||
private final Set<String> cwe; | |||
private final Set<String> owaspTop10; | |||
private final Set<String> sansTop25; | |||
private final Set<SQCategory> sq; | |||
private final SQCategory sqCategory; | |||
private final Set<SQCategory> ignoredSQCategories; | |||
private SecurityStandards(Set<String> standards, Set<String> cwe, Set<String> owaspTop10, Set<String> sansTop25, Set<SQCategory> sq) { | |||
private SecurityStandards(Set<String> standards, Set<String> cwe, Set<String> owaspTop10, Set<String> sansTop25, SQCategory sqCategory, Set<SQCategory> ignoredSQCategories) { | |||
this.standards = standards; | |||
this.cwe = cwe; | |||
this.owaspTop10 = owaspTop10; | |||
this.sansTop25 = sansTop25; | |||
this.sq = sq; | |||
this.sqCategory = sqCategory; | |||
this.ignoredSQCategories = ignoredSQCategories; | |||
} | |||
public Set<String> getStandards() { | |||
@@ -156,33 +169,42 @@ public final class SecurityStandards { | |||
return sansTop25; | |||
} | |||
public Set<SQCategory> getSq() { | |||
return sq; | |||
public SQCategory getSqCategory() { | |||
return sqCategory; | |||
} | |||
public Set<SQCategory> getIgnoredSQCategories() { | |||
return ignoredSQCategories; | |||
} | |||
/** | |||
* @throws IllegalStateException if {@code securityStandards} maps to multiple {@link SQCategory SQCategories} | |||
*/ | |||
public static SecurityStandards fromSecurityStandards(Set<String> securityStandards) { | |||
Set<String> standards = securityStandards.stream() | |||
.filter(Objects::nonNull) | |||
.collect(MoreCollectors.toSet()); | |||
Set<String> owaspTop10 = toOwaspTop10(standards); | |||
.collect(toSet()); | |||
Set<String> cwe = toCwe(standards); | |||
Set<String> owaspTop10 = toOwaspTop10(standards); | |||
Set<String> sansTop25 = toSansTop25(cwe); | |||
Set<SQCategory> sq = toSQCategories(cwe); | |||
return new SecurityStandards(standards, cwe, owaspTop10, sansTop25, sq); | |||
List<SQCategory> sq = toSortedSQCategories(cwe); | |||
SQCategory sqCategory = sq.iterator().next(); | |||
Set<SQCategory> ignoredSQCategories = sq.stream().skip(1).collect(Collectors.toSet()); | |||
return new SecurityStandards(standards, cwe, owaspTop10, sansTop25, sqCategory, ignoredSQCategories); | |||
} | |||
private static Set<String> toOwaspTop10(Set<String> securityStandards) { | |||
return securityStandards.stream() | |||
.filter(s -> s.startsWith(OWASP_TOP10_PREFIX)) | |||
.map(s -> s.substring(OWASP_TOP10_PREFIX.length())) | |||
.collect(MoreCollectors.toSet()); | |||
.collect(toSet()); | |||
} | |||
private static Set<String> toCwe(Collection<String> securityStandards) { | |||
Set<String> result = securityStandards.stream() | |||
.filter(s -> s.startsWith(CWE_PREFIX)) | |||
.map(s -> s.substring(CWE_PREFIX.length())) | |||
.collect(MoreCollectors.toSet()); | |||
.collect(toSet()); | |||
return result.isEmpty() ? singleton(UNKNOWN_STANDARD) : result; | |||
} | |||
@@ -191,15 +213,16 @@ public final class SecurityStandards { | |||
.keySet() | |||
.stream() | |||
.filter(k -> cwe.stream().anyMatch(CWES_BY_SANS_TOP_25.get(k)::contains)) | |||
.collect(MoreCollectors.toSet()); | |||
.collect(toSet()); | |||
} | |||
private static Set<SQCategory> toSQCategories(Collection<String> cwe) { | |||
Set<SQCategory> result = CWES_BY_SQ_CATEGORY | |||
private static List<SQCategory> toSortedSQCategories(Collection<String> cwe) { | |||
List<SQCategory> 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(SQCategory.OTHERS) : result; | |||
.sorted(SQ_CATEGORY_ORDERING) | |||
.collect(toList()); | |||
return result.isEmpty() ? singletonList(SQCategory.OTHERS) : result; | |||
} | |||
} |
@@ -139,7 +139,7 @@ public class IssueIndexerTest { | |||
assertThat(doc.getCwe()).containsExactlyInAnyOrder(SecurityStandards.UNKNOWN_STANDARD); | |||
assertThat(doc.getOwaspTop10()).isEmpty(); | |||
assertThat(doc.getSansTop25()).isEmpty(); | |||
assertThat(doc.getSonarSourceSecurityCategories()).containsOnly(SQCategory.OTHERS.getKey()); | |||
assertThat(doc.getSonarSourceSecurityCategory()).isEqualTo(SQCategory.OTHERS); | |||
} | |||
@Test |
@@ -20,13 +20,22 @@ | |||
package org.sonar.server.rule.index; | |||
import com.google.common.collect.ImmutableSet; | |||
import java.util.stream.Collectors; | |||
import com.tngtech.java.junit.dataprovider.DataProvider; | |||
import com.tngtech.java.junit.dataprovider.DataProviderRunner; | |||
import com.tngtech.java.junit.dataprovider.UseDataProvider; | |||
import java.util.EnumSet; | |||
import java.util.Random; | |||
import java.util.Set; | |||
import java.util.stream.IntStream; | |||
import java.util.stream.Stream; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.runner.RunWith; | |||
import org.sonar.api.rule.RuleStatus; | |||
import org.sonar.api.rule.Severity; | |||
import org.sonar.api.rules.RuleType; | |||
import org.sonar.api.utils.log.LogTester; | |||
import org.sonar.api.utils.log.LoggerLevel; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.DbTester; | |||
@@ -37,22 +46,31 @@ import org.sonar.db.rule.RuleDto.Scope; | |||
import org.sonar.db.rule.RuleMetadataDto; | |||
import org.sonar.db.rule.RuleTesting; | |||
import org.sonar.server.es.EsTester; | |||
import org.sonar.server.security.SecurityStandards; | |||
import org.sonar.server.security.SecurityStandards.SQCategory; | |||
import static com.google.common.collect.Sets.newHashSet; | |||
import static java.lang.String.format; | |||
import static java.util.Collections.emptyList; | |||
import static java.util.Collections.emptySet; | |||
import static java.util.stream.Collectors.joining; | |||
import static java.util.stream.Collectors.toSet; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.elasticsearch.index.query.QueryBuilders.termQuery; | |||
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.SecurityStandards.CWES_BY_SQ_CATEGORY; | |||
import static org.sonar.server.security.SecurityStandards.SQ_CATEGORY_KEYS_ORDERING; | |||
@RunWith(DataProviderRunner.class) | |||
public class RuleIndexerTest { | |||
@Rule | |||
public EsTester es = EsTester.create(); | |||
@Rule | |||
public DbTester dbTester = DbTester.create(); | |||
@Rule | |||
public LogTester logTester = new LogTester(); | |||
private DbClient dbClient = dbTester.getDbClient(); | |||
private final RuleIndexer underTest = new RuleIndexer(es.client(), dbClient); | |||
@@ -122,7 +140,7 @@ public class RuleIndexerTest { | |||
.get() | |||
.getHits() | |||
.getHits()[0] | |||
.getId()).isEqualTo(doc.getId()); | |||
.getId()).isEqualTo(doc.getId()); | |||
} | |||
@Test | |||
@@ -149,10 +167,48 @@ public class RuleIndexerTest { | |||
@Test | |||
public void index_long_rule_description() { | |||
String description = IntStream.range(0, 100000).map(i -> i % 100).mapToObj(Integer::toString).collect(Collectors.joining(" ")); | |||
String description = IntStream.range(0, 100000).map(i -> i % 100).mapToObj(Integer::toString).collect(joining(" ")); | |||
RuleDefinitionDto rule = dbTester.rules().insert(r -> r.setDescription(description)); | |||
underTest.commitAndIndex(dbTester.getSession(), rule.getId()); | |||
assertThat(es.countDocuments(TYPE_RULE)).isEqualTo(1); | |||
} | |||
@Test | |||
@UseDataProvider("twoDifferentCategoriesButOTHERS") | |||
public void log_a_warning_if_hotspot_rule_maps_to_multiple_SQCategories(SQCategory sqCategory1, SQCategory sqCategory2) { | |||
Set<String> standards = Stream.of(sqCategory1, sqCategory2) | |||
.flatMap(t -> CWES_BY_SQ_CATEGORY.get(t).stream().map(e -> "cwe:" + e)) | |||
.collect(toSet()); | |||
SecurityStandards securityStandards = SecurityStandards.fromSecurityStandards(standards); | |||
RuleDefinitionDto rule = dbTester.rules().insert(RuleTesting.newRule().setType(RuleType.SECURITY_HOTSPOT).setSecurityStandards(standards)); | |||
OrganizationDto organization = dbTester.organizations().insert(); | |||
underTest.commitAndIndex(dbTester.getSession(), rule.getId(), organization); | |||
assertThat(logTester.getLogs()).hasSize(1); | |||
assertThat(logTester.logs(LoggerLevel.WARN).get(0)) | |||
.isEqualTo(format( | |||
"Rule %s with CWEs '%s' maps to multiple SQ Security Categories: %s", | |||
rule.getKey(), | |||
String.join(", ", securityStandards.getCwe()), | |||
ImmutableSet.of(sqCategory1, sqCategory2).stream() | |||
.map(SQCategory::getKey) | |||
.sorted(SQ_CATEGORY_KEYS_ORDERING) | |||
.collect(joining(", ")))); | |||
} | |||
@DataProvider | |||
public static Object[][] twoDifferentCategoriesButOTHERS() { | |||
EnumSet<SQCategory> sqCategories = EnumSet.allOf(SQCategory.class); | |||
sqCategories.remove(SQCategory.OTHERS); | |||
// pick two random categories | |||
Random random = new Random(); | |||
SQCategory sqCategory1 = sqCategories.toArray(new SQCategory[0])[random.nextInt(sqCategories.size())]; | |||
sqCategories.remove(sqCategory1); | |||
SQCategory sqCategory2 = sqCategories.toArray(new SQCategory[0])[random.nextInt(sqCategories.size())]; | |||
return new Object[][] { | |||
{sqCategory1, sqCategory2} | |||
}; | |||
} | |||
} |
@@ -0,0 +1,110 @@ | |||
/* | |||
* 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 java.util.EnumSet; | |||
import java.util.Set; | |||
import java.util.stream.Collectors; | |||
import org.junit.Test; | |||
import org.sonar.server.security.SecurityStandards.SQCategory; | |||
import static java.util.Collections.emptySet; | |||
import static java.util.Collections.singleton; | |||
import static java.util.stream.Collectors.toSet; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.sonar.server.security.SecurityStandards.CWES_BY_SQ_CATEGORY; | |||
import static org.sonar.server.security.SecurityStandards.SQ_CATEGORY_KEYS_ORDERING; | |||
import static org.sonar.server.security.SecurityStandards.fromSecurityStandards; | |||
public class SecurityStandardsTest { | |||
@Test | |||
public void fromSecurityStandards_from_empty_set_has_SQCategory_OTHERS() { | |||
SecurityStandards securityStandards = fromSecurityStandards(emptySet()); | |||
assertThat(securityStandards.getStandards()).isEmpty(); | |||
assertThat(securityStandards.getSqCategory()).isEqualTo(SQCategory.OTHERS); | |||
assertThat(securityStandards.getIgnoredSQCategories()).isEmpty(); | |||
} | |||
@Test | |||
public void fromSecurityStandards_from_empty_set_has_unkwown_cwe_standard() { | |||
SecurityStandards securityStandards = fromSecurityStandards(emptySet()); | |||
assertThat(securityStandards.getStandards()).isEmpty(); | |||
assertThat(securityStandards.getCwe()).containsOnly("unknown"); | |||
} | |||
@Test | |||
public void fromSecurityStandards_from_empty_set_has_no_OwaspTop10_standard() { | |||
SecurityStandards securityStandards = fromSecurityStandards(emptySet()); | |||
assertThat(securityStandards.getStandards()).isEmpty(); | |||
assertThat(securityStandards.getOwaspTop10()).isEmpty(); | |||
} | |||
@Test | |||
public void fromSecurityStandards_from_empty_set_has_no_SansTop25_standard() { | |||
SecurityStandards securityStandards = fromSecurityStandards(emptySet()); | |||
assertThat(securityStandards.getStandards()).isEmpty(); | |||
assertThat(securityStandards.getSansTop25()).isEmpty(); | |||
} | |||
@Test | |||
public void fromSecurityStandards_finds_SQCategory_from_any_if_the_mapped_CWE_standard() { | |||
CWES_BY_SQ_CATEGORY.forEach((sqCategory, cwes) -> { | |||
cwes.forEach(cwe -> { | |||
SecurityStandards securityStandards = fromSecurityStandards(singleton("cwe:" + cwe)); | |||
assertThat(securityStandards.getSqCategory()).isEqualTo(sqCategory); | |||
}); | |||
}); | |||
} | |||
@Test | |||
public void fromSecurityStandards_finds_SQCategory_from_multiple_of_the_mapped_CWE_standard() { | |||
CWES_BY_SQ_CATEGORY.forEach((sqCategory, cwes) -> { | |||
SecurityStandards securityStandards = fromSecurityStandards(cwes.stream().map(t -> "cwe:" + t).collect(toSet())); | |||
assertThat(securityStandards.getSqCategory()).isEqualTo(sqCategory); | |||
}); | |||
} | |||
@Test | |||
public void fromSecurityStandards_finds_SQCategory_first_in_order_when_CWEs_map_to_multiple_SQCategories() { | |||
EnumSet<SQCategory> sqCategories = EnumSet.allOf(SQCategory.class); | |||
sqCategories.remove(SQCategory.OTHERS); | |||
while (!sqCategories.isEmpty()) { | |||
SQCategory expected = sqCategories.stream().min(SQ_CATEGORY_KEYS_ORDERING.onResultOf(SQCategory::getKey)).get(); | |||
SQCategory[] expectedIgnored = sqCategories.stream().filter(t -> t != expected).toArray(SQCategory[]::new); | |||
Set<String> cwes = sqCategories.stream() | |||
.flatMap(t -> CWES_BY_SQ_CATEGORY.get(t).stream().map(e -> "cwe:" + e)) | |||
.collect(Collectors.toSet()); | |||
SecurityStandards securityStandards = fromSecurityStandards(cwes); | |||
assertThat(securityStandards.getSqCategory()).isEqualTo(expected); | |||
assertThat(securityStandards.getIgnoredSQCategories()).containsOnly(expectedIgnored); | |||
sqCategories.remove(expected); | |||
} | |||
} | |||
} |
@@ -236,10 +236,7 @@ public class SearchAction implements HotspotsWsAction { | |||
Hotspots.Rule.Builder ruleBuilder = Hotspots.Rule.newBuilder(); | |||
for (RuleDefinitionDto rule : rules) { | |||
SecurityStandards securityStandards = SecurityStandards.fromSecurityStandards(rule.getSecurityStandards()); | |||
SecurityStandards.SQCategory sqCategory = securityStandards.getSq() | |||
.stream() | |||
.min(SecurityStandards.SQ_CATEGORY_ORDERING) | |||
.orElse(SecurityStandards.SQCategory.OTHERS); | |||
SecurityStandards.SQCategory sqCategory = securityStandards.getSqCategory(); | |||
ruleBuilder | |||
.clear() | |||
.setKey(rule.getKey().toString()) |