aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-webserver-es
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-webserver-es')
-rw-r--r--server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java110
-rw-r--r--server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java12
-rw-r--r--server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java35
-rw-r--r--server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java14
-rw-r--r--server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java63
-rw-r--r--server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java81
-rw-r--r--server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryTest.java9
7 files changed, 262 insertions, 62 deletions
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 72b3bd272f0..6b2c826f2c9 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
@@ -26,6 +26,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -54,6 +55,7 @@ import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.aggregations.HasAggregations;
+import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.filter.FiltersAggregator;
import org.elasticsearch.search.aggregations.bucket.filter.ParsedFilter;
@@ -77,12 +79,13 @@ import org.sonar.api.issue.IssueStatus;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.CleanCodeAttributeCategory;
-import org.sonar.core.rule.RuleType;
import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.api.server.rule.RulesDefinition.OwaspMobileTop10Version;
import org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version;
import org.sonar.api.server.rule.RulesDefinition.PciDssVersion;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.System2;
+import org.sonar.core.rule.RuleType;
import org.sonar.server.es.EsClient;
import org.sonar.server.es.EsUtils;
import org.sonar.server.es.SearchOptions;
@@ -120,10 +123,10 @@ import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
import static org.elasticsearch.search.aggregations.AggregationBuilders.filters;
import static org.elasticsearch.search.aggregations.AggregationBuilders.reverseNested;
-import static org.sonar.core.rule.RuleType.SECURITY_HOTSPOT;
-import static org.sonar.core.rule.RuleType.VULNERABILITY;
import static org.sonar.core.config.MQRModeConstants.MULTI_QUALITY_MODE_DEFAULT_VALUE;
import static org.sonar.core.config.MQRModeConstants.MULTI_QUALITY_MODE_ENABLED;
+import static org.sonar.core.rule.RuleType.SECURITY_HOTSPOT;
+import static org.sonar.core.rule.RuleType.VULNERABILITY;
import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars;
import static org.sonar.server.es.IndexType.FIELD_INDEX_TYPE;
import static org.sonar.server.es.searchrequest.TopAggregationDefinition.NON_STICKY;
@@ -145,6 +148,7 @@ import static org.sonar.server.issue.index.IssueIndex.Facet.IMPACT_SOFTWARE_QUAL
import static org.sonar.server.issue.index.IssueIndex.Facet.ISSUE_STATUSES;
import static org.sonar.server.issue.index.IssueIndex.Facet.LANGUAGES;
import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_ASVS_40;
+import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_MOBILE_TOP_10_2024;
import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_TOP_10;
import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_TOP_10_2021;
import static org.sonar.server.issue.index.IssueIndex.Facet.PCI_DSS_32;
@@ -185,6 +189,7 @@ import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_LINE
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_NEW_CODE_REFERENCE;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_NEW_STATUS;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_OWASP_ASVS_40;
+import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_OWASP_MOBILE_TOP_10_2024;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10_2021;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_PCI_DSS_32;
@@ -224,6 +229,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SOFT
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE_STATUSES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_LANGUAGES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_ASVS_40;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_MOBILE_TOP_10_2024;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10_2021;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PCI_DSS_32;
@@ -256,7 +262,6 @@ public class IssueIndex {
private static final String ISSUES_WITH_SECURITY_IMPACT = "issues_with_security_impact";
private static final String AGG_IMPACT_SEVERITIES = "impact_severities";
private static final String AGG_TO_REVIEW_SECURITY_HOTSPOTS = "toReviewSecurityHotspots";
- private static final String AGG_IN_REVIEW_SECURITY_HOTSPOTS = "inReviewSecurityHotspots";
private static final String AGG_REVIEWED_SECURITY_HOTSPOTS = "reviewedSecurityHotspots";
private static final String AGG_DISTRIBUTION = "distribution";
private static final BoolQueryBuilder NON_RESOLVED_VULNERABILITIES_FILTER = boolQuery()
@@ -266,10 +271,6 @@ public class IssueIndex {
.filter(nestedQuery(FIELD_ISSUE_IMPACTS, termsQuery(FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, SoftwareQuality.SECURITY.name()),
ScoreMode.Avg))
.mustNot(existsQuery(FIELD_ISSUE_RESOLUTION));
- private static final BoolQueryBuilder IN_REVIEW_HOTSPOTS_FILTER = boolQuery()
- .filter(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name()))
- .filter(termQuery(FIELD_ISSUE_STATUS, Issue.STATUS_IN_REVIEW))
- .mustNot(existsQuery(FIELD_ISSUE_RESOLUTION));
private static final BoolQueryBuilder TO_REVIEW_HOTSPOTS_FILTER = boolQuery()
.filter(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name()))
.filter(termQuery(FIELD_ISSUE_STATUS, Issue.STATUS_TO_REVIEW))
@@ -313,6 +314,7 @@ public class IssueIndex {
PCI_DSS_32(PARAM_PCI_DSS_32, FIELD_ISSUE_PCI_DSS_32, STICKY, DEFAULT_FACET_SIZE),
PCI_DSS_40(PARAM_PCI_DSS_40, FIELD_ISSUE_PCI_DSS_40, STICKY, DEFAULT_FACET_SIZE),
OWASP_ASVS_40(PARAM_OWASP_ASVS_40, FIELD_ISSUE_OWASP_ASVS_40, STICKY, DEFAULT_FACET_SIZE),
+ OWASP_MOBILE_TOP_10_2024(PARAM_OWASP_MOBILE_TOP_10_2024, FIELD_ISSUE_OWASP_MOBILE_TOP_10_2024, STICKY, DEFAULT_FACET_SIZE),
OWASP_TOP_10(PARAM_OWASP_TOP_10, FIELD_ISSUE_OWASP_TOP_10, STICKY, DEFAULT_FACET_SIZE),
OWASP_TOP_10_2021(PARAM_OWASP_TOP_10_2021, FIELD_ISSUE_OWASP_TOP_10_2021, STICKY, DEFAULT_FACET_SIZE),
STIG_ASD_V5R3(PARAM_STIG_ASD_V5R3, FIELD_ISSUE_STIG_ASD_V5R3, STICKY, DEFAULT_FACET_SIZE),
@@ -531,6 +533,7 @@ public class IssueIndex {
addSecurityCategoryPrefixFilter(FIELD_ISSUE_PCI_DSS_32, PCI_DSS_32, query.pciDss32(), filters);
addSecurityCategoryPrefixFilter(FIELD_ISSUE_PCI_DSS_40, PCI_DSS_40, query.pciDss40(), filters);
addOwaspAsvsFilter(FIELD_ISSUE_OWASP_ASVS_40, OWASP_ASVS_40, query, filters);
+ addSecurityCategoryFilter(FIELD_ISSUE_OWASP_MOBILE_TOP_10_2024, OWASP_MOBILE_TOP_10_2024, query.owaspMobileTop10For2024(), filters);
addSecurityCategoryFilter(FIELD_ISSUE_OWASP_TOP_10, OWASP_TOP_10, query.owaspTop10(), filters);
addSecurityCategoryFilter(FIELD_ISSUE_OWASP_TOP_10_2021, OWASP_TOP_10_2021, query.owaspTop10For2021(), filters);
addSecurityCategoryFilter(FIELD_ISSUE_STIG_ASD_V5R3, STIG_ASD_V5R3, query.stigAsdV5R3(), filters);
@@ -543,9 +546,8 @@ public class IssueIndex {
addImpactFilters(query, filters);
addComponentRelatedFilters(query, filters);
addDatesFilter(filters, query);
- addCreatedAfterByProjectsFilter(filters, query);
+ addNewCodeByProjectsFilter(filters, query);
addNewCodeReferenceFilter(filters, query);
- addNewCodeReferenceFilterByProjectsFilter(filters, query);
return filters;
}
@@ -869,33 +871,23 @@ public class IssueIndex {
if (newCodeOnReference != null) {
filters.addFilter(
FIELD_ISSUE_NEW_CODE_REFERENCE, new SimpleFieldFilterScope(FIELD_ISSUE_NEW_CODE_REFERENCE),
- termQuery(FIELD_ISSUE_NEW_CODE_REFERENCE, true));
- }
- }
-
- private static void addNewCodeReferenceFilterByProjectsFilter(AllFilters allFilters, IssueQuery query) {
- Collection<String> newCodeOnReferenceByProjectUuids = query.newCodeOnReferenceByProjectUuids();
- BoolQueryBuilder boolQueryBuilder = boolQuery();
-
- if (!newCodeOnReferenceByProjectUuids.isEmpty()) {
-
- newCodeOnReferenceByProjectUuids.forEach(projectOrProjectBranchUuid -> boolQueryBuilder.should(boolQuery()
- .filter(termQuery(FIELD_ISSUE_BRANCH_UUID, projectOrProjectBranchUuid))
- .filter(termQuery(FIELD_ISSUE_NEW_CODE_REFERENCE, true))));
-
- allFilters.addFilter("__is_new_code_reference_by_project_uuids",
- new SimpleFieldFilterScope("newCodeReferenceByProjectUuids"), boolQueryBuilder);
+ termQuery(FIELD_ISSUE_NEW_CODE_REFERENCE, newCodeOnReference));
}
}
- private static void addCreatedAfterByProjectsFilter(AllFilters allFilters, IssueQuery query) {
+ private static void addNewCodeByProjectsFilter(AllFilters allFilters, IssueQuery query) {
Map<String, PeriodStart> createdAfterByProjectUuids = query.createdAfterByProjectUuids();
BoolQueryBuilder boolQueryBuilder = boolQuery();
createdAfterByProjectUuids.forEach((projectOrProjectBranchUuid, createdAfterDate) -> boolQueryBuilder.should(boolQuery()
.filter(termQuery(FIELD_ISSUE_BRANCH_UUID, projectOrProjectBranchUuid))
.filter(rangeQuery(FIELD_ISSUE_FUNC_CREATED_AT).from(createdAfterDate.date().getTime(), createdAfterDate.inclusive()))));
- allFilters.addFilter("__created_after_by_project_uuids", new SimpleFieldFilterScope("createdAfterByProjectUuids"), boolQueryBuilder);
+ Collection<String> newCodeOnReferenceByProjectUuids = query.newCodeOnReferenceByProjectUuids();
+ newCodeOnReferenceByProjectUuids.forEach(projectOrProjectBranchUuid -> boolQueryBuilder.should(boolQuery()
+ .filter(termQuery(FIELD_ISSUE_BRANCH_UUID, projectOrProjectBranchUuid))
+ .filter(termQuery(FIELD_ISSUE_NEW_CODE_REFERENCE, true))));
+
+ allFilters.addFilter("__new_code_by_project_uuids", new SimpleFieldFilterScope("newCodeByProjectUuids"), boolQueryBuilder);
}
private void validateCreationDateBounds(@Nullable Date createdBefore, @Nullable Date createdAfter) {
@@ -925,6 +917,7 @@ public class IssueIndex {
addSecurityCategoryFacetIfNeeded(PARAM_PCI_DSS_32, PCI_DSS_32, options, aggregationHelper, esRequest, query.pciDss32().toArray());
addSecurityCategoryFacetIfNeeded(PARAM_PCI_DSS_40, PCI_DSS_40, options, aggregationHelper, esRequest, query.pciDss40().toArray());
addSecurityCategoryFacetIfNeeded(PARAM_OWASP_ASVS_40, OWASP_ASVS_40, options, aggregationHelper, esRequest, query.owaspAsvs40().toArray());
+ addSecurityCategoryFacetIfNeeded(PARAM_OWASP_MOBILE_TOP_10_2024, OWASP_MOBILE_TOP_10_2024, options, aggregationHelper, esRequest, query.owaspMobileTop10For2024().toArray());
addSecurityCategoryFacetIfNeeded(PARAM_OWASP_TOP_10, OWASP_TOP_10, options, aggregationHelper, esRequest, query.owaspTop10().toArray());
addSecurityCategoryFacetIfNeeded(PARAM_OWASP_TOP_10_2021, OWASP_TOP_10_2021, options, aggregationHelper, esRequest, query.owaspTop10For2021().toArray());
addSecurityCategoryFacetIfNeeded(PARAM_STIG_ASD_V5R3, STIG_ASD_V5R3, options, aggregationHelper, esRequest, query.stigAsdV5R3().toArray());
@@ -1301,7 +1294,7 @@ public class IssueIndex {
}
private static SecurityStandardCategoryStatistics emptyCweStatistics(String rule) {
- return new SecurityStandardCategoryStatistics(rule, 0, OptionalInt.of(1), 0, 0, 1, null, null);
+ return new SecurityStandardCategoryStatistics(rule, 0, OptionalInt.of(1), 0, 0, 1, null, null, Map.of());
}
public List<SecurityStandardCategoryStatistics> getSonarSourceReport(String projectUuid, boolean isViewOrApp, boolean includeCwe) {
@@ -1347,6 +1340,17 @@ public class IssueIndex {
return searchWithLevelDistribution(request, version.label(), Integer.toString(level));
}
+ public List<SecurityStandardCategoryStatistics> getOwaspMobileTop10Report(String projectUuid, boolean isViewOrApp, boolean includeCwe, OwaspMobileTop10Version version) {
+ SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
+ IntStream.rangeClosed(1, 10).mapToObj(i -> "m" + i)
+ .forEach(owaspMobileCategory -> request.aggregation(
+ newSecurityReportSubAggregations(
+ AggregationBuilders.filter(owaspMobileCategory, boolQuery().filter(termQuery(version.prefix(), owaspMobileCategory))),
+ includeCwe,
+ null)));
+ return search(request, includeCwe, version.label());
+ }
+
public List<SecurityStandardCategoryStatistics> getOwaspTop10Report(String projectUuid, boolean isViewOrApp, boolean includeCwe, OwaspTop10Version version) {
SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
IntStream.rangeClosed(1, 10).mapToObj(i -> "a" + i)
@@ -1446,10 +1450,11 @@ public class IssueIndex {
Aggregation severitiesAggregations =
((ParsedFilter) categoryBucket.getAggregations().get(AGG_VULNERABILITIES)).getAggregations().get(AGG_SEVERITIES);
- CountAndRating countAndRating = getCountAndRating(severitiesAggregations);
- long vulnerabilities = countAndRating.getCount();
+ SeverityAggregationDetails severityAggregationDetails = getSeverityDetails(severitiesAggregations);
+ long vulnerabilities = severityAggregationDetails.getCount();
// Worst severity having at least one issue
- OptionalInt severityRating = countAndRating.getRating();
+ OptionalInt severityRating = severityAggregationDetails.getRating();
+ Map<String, Long> severityDistribution = severityAggregationDetails.getDistribution();
long toReviewSecurityHotspots = ((ParsedValueCount) ((ParsedFilter) categoryBucket.getAggregations().get(AGG_TO_REVIEW_SECURITY_HOTSPOTS)).getAggregations().get(AGG_COUNT))
.getValue();
@@ -1460,32 +1465,39 @@ public class IssueIndex {
Integer securityReviewRating = computeRating(percent.orElse(null)).getIndex();
return new SecurityStandardCategoryStatistics(categoryName, vulnerabilities, severityRating, toReviewSecurityHotspots,
- reviewedSecurityHotspots, securityReviewRating, children, version);
+ reviewedSecurityHotspots, securityReviewRating, children, version, severityDistribution);
}
- private CountAndRating getCountAndRating(Aggregation severitiesAggregations) {
+ private SeverityAggregationDetails getSeverityDetails(Aggregation severitiesAggregations) {
+ List<? extends Terms.Bucket> severityBuckets;
+ long vulnerabilities;
+ OptionalInt severityRating;
if (isMQRMode()) {
- List<? extends Terms.Bucket> severityBuckets =
+ severityBuckets =
((ParsedStringTerms) ((ParsedFilter) ((ParsedNested) severitiesAggregations).getAggregations().get(ISSUES_WITH_SECURITY_IMPACT)).getAggregations().get(AGG_IMPACT_SEVERITIES)).getBuckets();
- long vulnerabilities =
+ vulnerabilities =
severityBuckets.stream().mapToLong(b -> ((ParsedValueCount) b.getAggregations().get(AGG_COUNT)).getValue()).sum();
// Worst severity having at least one issue
- OptionalInt severityRating = severityBuckets.stream()
+ severityRating = severityBuckets.stream()
.filter(b -> ((ParsedValueCount) b.getAggregations().get(AGG_COUNT)).getValue() != 0)
.mapToInt(b -> org.sonar.api.issue.impact.Severity.valueOf(b.getKeyAsString()).ordinal() + 1)
.max();
- return new CountAndRating(vulnerabilities, severityRating);
} else {
- List<? extends Terms.Bucket> severityBuckets = ((ParsedStringTerms) severitiesAggregations).getBuckets();
- long vulnerabilities =
+ severityBuckets = ((ParsedStringTerms) severitiesAggregations).getBuckets();
+ vulnerabilities =
severityBuckets.stream().mapToLong(b -> ((ParsedValueCount) b.getAggregations().get(AGG_COUNT)).getValue()).sum();
// Worst severity having at least one issue
- OptionalInt severityRating = severityBuckets.stream()
+ severityRating = severityBuckets.stream()
.filter(b -> ((ParsedValueCount) b.getAggregations().get(AGG_COUNT)).getValue() != 0)
.mapToInt(b -> Severity.ALL.indexOf(b.getKeyAsString()) + 1)
.max();
- return new CountAndRating(vulnerabilities, severityRating);
}
+ Map<String, Long> severityDistribution = severityBuckets.stream()
+ .collect(Collectors.toMap(
+ e -> e.getKeyAsString().toLowerCase(Locale.US),
+ MultiBucketsAggregation.Bucket::getDocCount
+ ));
+ return new SeverityAggregationDetails(vulnerabilities, severityRating, severityDistribution);
}
private AggregationBuilder newSecurityReportSubAggregations(AggregationBuilder categoriesAggs, String securityStandardVersionPrefix) {
@@ -1523,9 +1535,6 @@ public class IssueIndex {
.subAggregation(AggregationBuilders.filter(AGG_TO_REVIEW_SECURITY_HOTSPOTS, TO_REVIEW_HOTSPOTS_FILTER)
.subAggregation(
AggregationBuilders.count(AGG_COUNT).field(FIELD_ISSUE_KEY)))
- .subAggregation(AggregationBuilders.filter(AGG_IN_REVIEW_SECURITY_HOTSPOTS, IN_REVIEW_HOTSPOTS_FILTER)
- .subAggregation(
- AggregationBuilders.count(AGG_COUNT).field(FIELD_ISSUE_KEY)))
.subAggregation(AggregationBuilders.filter(AGG_REVIEWED_SECURITY_HOTSPOTS, REVIEWED_HOTSPOTS_FILTER)
.subAggregation(
AggregationBuilders.count(AGG_COUNT).field(FIELD_ISSUE_KEY)));
@@ -1569,7 +1578,6 @@ public class IssueIndex {
componentFilter
.should(getNonResolvedIssuesOrNonResolvedSecurityImpactQueryBuilderBasedOnMode())
.should(TO_REVIEW_HOTSPOTS_FILTER)
- .should(IN_REVIEW_HOTSPOTS_FILTER)
.should(REVIEWED_HOTSPOTS_FILTER)
.minimumShouldMatch(1))
.size(0);
@@ -1580,13 +1588,15 @@ public class IssueIndex {
}
- private static class CountAndRating {
+ private static class SeverityAggregationDetails {
private long count;
private OptionalInt rating;
+ private Map<String, Long> distribution;
- public CountAndRating(long count, OptionalInt rating) {
+ public SeverityAggregationDetails(long count, OptionalInt rating, Map<String, Long> distribution) {
this.count = count;
this.rating = rating;
+ this.distribution = distribution;
}
public long getCount() {
@@ -1596,5 +1606,9 @@ public class IssueIndex {
public OptionalInt getRating() {
return rating;
}
+
+ public Map<String, Long> getDistribution() {
+ return distribution;
+ }
}
}
diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java
index ddf06536376..9a20bac4775 100644
--- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java
+++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java
@@ -74,6 +74,7 @@ public class IssueQuery {
private final Collection<String> languages;
private final Collection<String> tags;
private final Collection<String> types;
+ private final Collection<String> owaspMobileTop10For2024;
private final Collection<String> owaspTop10;
private final Collection<String> pciDss32;
private final Collection<String> pciDss40;
@@ -129,6 +130,7 @@ public class IssueQuery {
this.pciDss40 = defaultCollection(builder.pciDss40);
this.owaspAsvs40 = defaultCollection(builder.owaspAsvs40);
this.owaspAsvsLevel = builder.owaspAsvsLevel;
+ this.owaspMobileTop10For2024 = defaultCollection(builder.owaspMobileTop10For2024);
this.owaspTop10 = defaultCollection(builder.owaspTop10);
this.owaspTop10For2021 = defaultCollection(builder.owaspTop10For2021);
this.stigAsdV5R3 = defaultCollection(builder.stigAsdV5R3);
@@ -256,6 +258,10 @@ public class IssueQuery {
return Optional.ofNullable(owaspAsvsLevel);
}
+ public Collection<String> owaspMobileTop10For2024() {
+ return owaspMobileTop10For2024;
+ }
+
public Collection<String> owaspTop10() {
return owaspTop10;
}
@@ -402,6 +408,7 @@ public class IssueQuery {
private Collection<String> pciDss40;
private Collection<String> owaspAsvs40;
private Integer owaspAsvsLevel;
+ private Collection<String> owaspMobileTop10For2024;
private Collection<String> owaspTop10;
private Collection<String> owaspTop10For2021;
private Collection<String> stigAsdV5R3;
@@ -556,6 +563,11 @@ public class IssueQuery {
return this;
}
+ public Builder owaspMobileTop10For2024(@Nullable Collection<String> o) {
+ this.owaspMobileTop10For2024 = o;
+ return this;
+ }
+
public Builder owaspTop10(@Nullable Collection<String> o) {
this.owaspTop10 = o;
return this;
diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java
index e105cd9d175..39e1189e5a6 100644
--- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java
+++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java
@@ -150,6 +150,7 @@ public class IssueQueryFactory {
.pciDss40(request.getPciDss40())
.owaspAsvs40(request.getOwaspAsvs40())
.owaspAsvsLevel(request.getOwaspAsvsLevel())
+ .owaspMobileTop10For2024(request.getOwaspMobileTop10For2024())
.owaspTop10(request.getOwaspTop10())
.owaspTop10For2021(request.getOwaspTop10For2021())
.stigAsdR5V3(request.getStigAsdV5R3())
@@ -167,6 +168,8 @@ public class IssueQueryFactory {
List<ComponentDto> allComponents = new ArrayList<>();
boolean effectiveOnComponentOnly = mergeDeprecatedComponentParameters(dbSession, request, allComponents);
addComponentParameters(builder, dbSession, effectiveOnComponentOnly, allComponents, request);
+ // SONAR-25108
+ unsetMainBranch(builder, issueKeys != null && !issueKeys.isEmpty(), allComponents, request);
setCreatedAfterFromRequest(dbSession, builder, request, allComponents, timeZone);
String sort = request.getSort();
@@ -266,18 +269,22 @@ public class IssueQueryFactory {
ComponentDto component = componentUuids.iterator().next();
if (!QUALIFIERS_WITHOUT_LEAK_PERIOD.contains(component.qualifier()) && request.getPullRequest() == null) {
- Optional<SnapshotDto> snapshot = getLastAnalysis(dbSession, component);
- if (!snapshot.isEmpty() && isLastAnalysisFromReAnalyzedReferenceBranch(dbSession, snapshot.get())) {
- builder.newCodeOnReference(true);
- return;
- }
- // if last analysis has no period date, then no issue should be considered new.
- Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(snapshot);
- setCreatedAfterFromDates(builder, createdAfterFromSnapshot, null, false);
+ setInNewCodePeriod(dbSession, builder, component.uuid());
}
}
}
+ private void setInNewCodePeriod(DbSession dbSession, IssueQuery.Builder builder, String componentUuid) {
+ Optional<SnapshotDto> snapshot = getLastAnalysis(dbSession, componentUuid);
+ if (!snapshot.isEmpty() && isLastAnalysisFromReAnalyzedReferenceBranch(dbSession, snapshot.get())) {
+ builder.newCodeOnReference(true);
+ return;
+ }
+ // if last analysis has no period date, then no issue should be considered new.
+ Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(snapshot);
+ setCreatedAfterFromDates(builder, createdAfterFromSnapshot, null, false);
+ }
+
private static boolean notInNewCodePeriod(SearchRequest request) {
Boolean inNewCodePeriod = request.getInNewCodePeriod();
inNewCodePeriod = Boolean.TRUE.equals(inNewCodePeriod);
@@ -298,8 +305,8 @@ public class IssueQueryFactory {
.isPresent();
}
- private Optional<SnapshotDto> getLastAnalysis(DbSession dbSession, ComponentDto component) {
- return dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, component.uuid());
+ private Optional<SnapshotDto> getLastAnalysis(DbSession dbSession, String componentUuid) {
+ return dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, componentUuid);
}
private List<SnapshotDto> getLastAnalysis(DbSession dbSession, Set<String> projectUuids) {
@@ -511,4 +518,12 @@ public class IssueQueryFactory {
builder.mainBranch(branchDto.isMain());
}
}
+
+ private static void unsetMainBranch(IssueQuery.Builder builder, boolean hasIssueKey, List<ComponentDto> components, SearchRequest request) {
+ var pullRequest = request.getPullRequest();
+ var branch = request.getBranch();
+ if ((components.isEmpty() || UNKNOWN_COMPONENT.equals(components.get(0)) || (pullRequest == null && branch == null)) && hasIssueKey) {
+ builder.mainBranch(null);
+ }
+ }
}
diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java
index 23daad4fed0..e0fa9ca61cc 100644
--- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java
+++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java
@@ -456,7 +456,7 @@ class IssueIndexFiltersTest extends IssueIndexTestCommon {
}
@Test
- void filter_by_new_code_reference_branches() {
+ void filter_by_new_reference_branches() {
ComponentDto project1 = db.components().insertPrivateProject().getMainBranchComponent();
IssueDoc project1Issue1 = newDocForProject(project1).setIsNewCodeReference(true);
IssueDoc project1Issue2 = newDocForProject(project1).setIsNewCodeReference(false);
@@ -474,14 +474,20 @@ class IssueIndexFiltersTest extends IssueIndexTestCommon {
IssueDoc project2Branch1Issue1 = newDoc(project2Branch1, project2.uuid()).setIsNewCodeReference(false);
IssueDoc project2Branch1Issue2 = newDoc(project2Branch1, project2.uuid()).setIsNewCodeReference(true);
+ ComponentDto project3 = db.components().insertPrivateProject().getMainBranchComponent();
+ ComponentDto project3Branch1 = db.components().insertProjectBranch(project2);
+ IssueDoc project3Issue1 = newDoc(project3Branch1, project3.uuid()).setFuncCreationDate(new Date(1000L));
+ IssueDoc project3Issue2 = newDoc(project3Branch1, project3.uuid()).setFuncCreationDate(new Date(2000L));
+
indexIssues(project1Issue1, project1Issue2, project2Issue1, project2Issue2,
- project1Branch1Issue1, project1Branch1Issue2, project2Branch1Issue1, project2Branch1Issue2);
+ project1Branch1Issue1, project1Branch1Issue2, project2Branch1Issue1, project2Branch1Issue2, project3Issue1, project3Issue2);
// Search for issues of project 1 branch 1 and project 2 branch 1 that are new code on a branch using reference for new code
assertThatSearchReturnsOnly(IssueQuery.builder()
.mainBranch(false)
- .newCodeOnReferenceByProjectUuids(Set.of(project1Branch1.uuid(), project2Branch1.uuid())),
- project1Branch1Issue2.key(), project2Branch1Issue2.key());
+ .newCodeOnReferenceByProjectUuids(Set.of(project1Branch1.uuid(), project2Branch1.uuid()))
+ .createdAfterByProjectUuids(Map.of(project3Branch1.uuid(), new IssueQuery.PeriodStart(new Date(1500), false))),
+ project1Branch1Issue2.key(), project2Branch1Issue2.key(), project3Issue2.key());
}
@Test
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 0eee21734c2..0c002a4e13c 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
@@ -30,7 +30,9 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.IssueStatus;
import org.sonar.api.rule.Severity;
+import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.core.rule.RuleType;
import org.sonar.api.server.rule.RulesDefinition.StigVersion;
import org.sonar.db.component.ComponentDto;
@@ -51,6 +53,7 @@ import static org.sonar.api.issue.impact.Severity.MEDIUM;
import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY;
import static org.sonar.api.issue.impact.SoftwareQuality.SECURITY;
import static org.sonar.api.server.rule.RulesDefinition.OwaspAsvsVersion;
+import static org.sonar.api.server.rule.RulesDefinition.OwaspMobileTop10Version.Y2024;
import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2017;
import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2021;
import static org.sonar.api.server.rule.RulesDefinition.PciDssVersion;
@@ -357,6 +360,31 @@ class IssueIndexSecurityReportsTest extends IssueIndexTestCommon {
tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 5));
}
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ void getOwaspMobileTop10For2024Report_aggregation_with_cwe(boolean mqrMode) {
+ doReturn(Optional.of(mqrMode)).when(config).getBoolean(MULTI_QUALITY_MODE_ENABLED);
+ List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspMobile2024Report(true);
+
+ Map<String, List<SecurityStandardCategoryStatistics>> cweByOwasp = owaspTop10Report.stream()
+ .collect(Collectors.toMap(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getChildren));
+
+ assertThat(cweByOwasp.get("m1")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
+ SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
+ SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
+ .containsExactlyInAnyOrder(
+ tuple("123", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
+ tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
+ tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 5));
+ assertThat(cweByOwasp.get("m3")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
+ SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
+ SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
+ .containsExactlyInAnyOrder(
+ tuple("123", 2L /* openvul1, openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
+ tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
+ tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 5));
+ }
+
private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwaspReport(boolean includeCwe) {
ComponentDto project = newPrivateProjectDto();
indexIssues(
@@ -545,6 +573,41 @@ class IssueIndexSecurityReportsTest extends IssueIndexTestCommon {
return owaspTop10Report;
}
+ private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwaspMobile2024Report(boolean includeCwe) {
+ ComponentDto project = newPrivateProjectDto();
+ indexIssues(
+ newDocForProject("openvul1", project).setOwaspMobileTop10For2024(asList("m1", "m3")).setCwe(asList("123", "456")).setType(RuleType.VULNERABILITY).setImpacts(Map.of(SECURITY, MEDIUM)).setStatus(IssueStatus.OPEN.name())
+ .setSeverity(Severity.MAJOR),
+ newDocForProject("openvul2", project).setOwaspMobileTop10For2024(asList("m3", "m6")).setCwe(List.of("123")).setType(RuleType.VULNERABILITY).setImpacts(Map.of(SECURITY, LOW)).setStatus(IssueStatus.OPEN.name())
+ .setSeverity(Severity.MINOR),
+ newDocForProject("notowaspvul", project).setOwaspMobileTop10For2024(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setImpacts(Map.of(SECURITY, HIGH)).setStatus(IssueStatus.OPEN.toString())
+ .setSeverity(Severity.CRITICAL),
+ newDocForProject("toreviewhotspot1", project).setOwaspMobileTop10For2024(asList("m1", "m3")).setCwe(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT)
+ .setStatus(Issue.STATUS_TO_REVIEW),
+ newDocForProject("toreviewhotspot2", project).setOwaspMobileTop10For2024(asList("m3", "m6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
+ newDocForProject("reviewedHotspot", project).setOwaspMobileTop10For2024(asList("m3", "m8")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
+ .setResolution(Issue.RESOLUTION_FIXED),
+ newDocForProject("notowasphotspot", project).setOwaspMobileTop10For2024(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
+
+ List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspMobileTop10Report(project.uuid(), false, includeCwe, Y2024);
+ assertThat(owaspTop10Report)
+ .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
+ SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
+ SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
+ .containsExactlyInAnyOrder(
+ tuple("m1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L, 5),
+ tuple("m2", 0L, OptionalInt.empty(), 0L, 0L, 1),
+ tuple("m3", 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* reviewedHotspot */, 4),
+ tuple("m4", 0L, OptionalInt.empty(), 0L, 0L, 1),
+ tuple("m5", 0L, OptionalInt.empty(), 0L, 0L, 1),
+ tuple("m6", 1L /* openvul2 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 5),
+ tuple("m7", 0L, OptionalInt.empty(), 0L, 0L, 1),
+ tuple("m8", 0L, OptionalInt.empty(), 0L, 1L /* reviewedHotspot */, 1),
+ tuple("m9", 0L, OptionalInt.empty(), 0L, 0L, 1),
+ tuple("m10", 0L, OptionalInt.empty(), 0L, 0L, 1));
+ return owaspTop10Report;
+ }
+
@ParameterizedTest
@ValueSource(booleans = {true, false})
void getPciDssReport_aggregation_on_portfolio(boolean mqrMode) {
diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java
index eb5c05b4ab6..c62293eb309 100644
--- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java
+++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java
@@ -25,6 +25,7 @@ import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
+import java.util.List;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
@@ -741,4 +742,84 @@ public class IssueQueryFactoryTest {
.hasMessageContaining("'unknown-date' cannot be parsed as either a date or date+time");
}
+ @Test
+ public void when_issue_keys_provided_with_no_component_should_not_have_main_branch() {
+ SearchRequest request = new SearchRequest()
+ .setIssues(List.of("issue-key-1", "issue-key-2"));
+
+ IssueQuery query = underTest.create(request);
+
+ assertThat(query.isMainBranch()).isNull();
+ }
+
+ @Test
+ public void when_issue_keys_and_component_provided_should_have_main_branch_set() {
+ // Create a project with main branch
+ ProjectData projectData = db.components().insertPrivateProject();
+ ComponentDto mainBranch = projectData.getMainBranchComponent();
+ String branchName = DEFAULT_MAIN_BRANCH_NAME;
+
+ // Request with issue keys and main branch
+ SearchRequest request = new SearchRequest()
+ .setIssues(List.of("issue-key-1", "issue-key-2"))
+ .setComponentKeys(List.of(mainBranch.getKey()))
+ .setBranch(branchName);
+
+ IssueQuery query = underTest.create(request);
+
+ // Should unset main branch since issue keys are provided
+ assertThat(query.isMainBranch()).isTrue();
+ assertThat(query.branchUuid()).isEqualTo(mainBranch.uuid());
+ }
+
+ @Test
+ public void when_no_issue_keys_provided_should_default_to_main_branch() {
+ ProjectData projectData = db.components().insertPrivateProject();
+ ComponentDto mainBranch = projectData.getMainBranchComponent();
+ String branchName = DEFAULT_MAIN_BRANCH_NAME;
+
+ SearchRequest request = new SearchRequest()
+ .setComponentKeys(List.of(mainBranch.getKey()))
+ .setBranch(branchName);
+
+ IssueQuery query = underTest.create(request);
+
+ assertThat(query.isMainBranch()).isTrue();
+ assertThat(query.branchUuid()).isEqualTo(mainBranch.uuid());
+ }
+
+ @Test
+ public void when_component_is_non_main_branch_should_not_default_to_main_branch() {
+ ProjectData projectData = db.components().insertPrivateProject();
+ ComponentDto mainBranch = projectData.getMainBranchComponent();
+ String branchName = "feature-branch";
+ ComponentDto branch = db.components().insertProjectBranch(mainBranch, b -> b.setKey(branchName));
+
+ SearchRequest request = new SearchRequest()
+ .setComponentKeys(List.of(branch.getKey()))
+ .setBranch(branchName);
+
+ IssueQuery query = underTest.create(request);
+
+ assertThat(query.isMainBranch()).isFalse();
+ assertThat(query.branchUuid()).isEqualTo(branch.uuid());
+ }
+
+ @Test
+ public void when_empty_issue_keys_list_provided_should_default_to_main_branch() {
+ ProjectData projectData = db.components().insertPrivateProject();
+ ComponentDto mainBranch = projectData.getMainBranchComponent();
+ String branchName = DEFAULT_MAIN_BRANCH_NAME;
+
+ SearchRequest request = new SearchRequest()
+ .setIssues(Collections.emptyList())
+ .setComponentKeys(List.of(mainBranch.getKey()))
+ .setBranch(branchName);
+
+ IssueQuery query = underTest.create(request);
+
+ assertThat(query.isMainBranch()).isTrue();
+ assertThat(query.branchUuid()).isEqualTo(mainBranch.uuid());
+ }
+
}
diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryTest.java
index c8b9870d547..979e493da47 100644
--- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryTest.java
+++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryTest.java
@@ -128,6 +128,15 @@ class IssueQueryTest {
}
@Test
+ void build_owasp_mobile_query() {
+ IssueQuery query = IssueQuery.builder()
+ .owaspMobileTop10For2024(List.of("m5", "m6"))
+ .build();
+
+ assertThat(query.owaspMobileTop10For2024()).containsOnly("m5", "m6");
+ }
+
+ @Test
void build_stig_query() {
IssueQuery query = IssueQuery.builder()
.stigAsdR5V3(List.of("V-222400", "V-222401"))