aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/index/SecurityStandardCategoryStatistics.java13
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/codequalityissue/CodeQualityIssueWorkflowDefinition.java10
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/issue/index/SecurityStandardCategoryStatisticsTest.java14
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowForCodeQualityIssuesTest.java22
-rw-r--r--server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java48
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileRepositoryImpl.java2
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileRepositoryImplTest.java49
7 files changed, 130 insertions, 28 deletions
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/SecurityStandardCategoryStatistics.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/SecurityStandardCategoryStatistics.java
index 484b03f1918..903fe411831 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/SecurityStandardCategoryStatistics.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/SecurityStandardCategoryStatistics.java
@@ -20,6 +20,7 @@
package org.sonar.server.issue.index;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
import javax.annotation.Nullable;
@@ -38,9 +39,11 @@ public class SecurityStandardCategoryStatistics {
private boolean hasMoreRules;
private final Optional<String> version;
private Optional<String> level = Optional.empty();
+ private final Map<String, Long> severityDistribution;
public SecurityStandardCategoryStatistics(String category, long vulnerabilities, OptionalInt vulnerabiliyRating, long toReviewSecurityHotspots,
- long reviewedSecurityHotspots, Integer securityReviewRating, @Nullable List<SecurityStandardCategoryStatistics> children, @Nullable String version) {
+ long reviewedSecurityHotspots, Integer securityReviewRating, @Nullable List<SecurityStandardCategoryStatistics> children, @Nullable String version,
+ Map<String, Long> severityDistribution) {
this.category = category;
this.vulnerabilities = vulnerabilities;
this.vulnerabilityRating = vulnerabiliyRating;
@@ -50,6 +53,7 @@ public class SecurityStandardCategoryStatistics {
this.children = children;
this.version = Optional.ofNullable(version);
this.hasMoreRules = false;
+ this.severityDistribution = severityDistribution;
}
public SecurityStandardCategoryStatistics withModifiedVulnerabilities(
@@ -71,7 +75,8 @@ public class SecurityStandardCategoryStatistics {
this.getReviewedSecurityHotspots(),
this.getSecurityReviewRating(),
this.getChildren(),
- this.getVersion().orElse(null));
+ this.getVersion().orElse(null),
+ this.getSeverityDistribution());
}
public String getCategory() {
@@ -140,4 +145,8 @@ public class SecurityStandardCategoryStatistics {
public void setHasMoreRules(boolean hasMoreRules) {
this.hasMoreRules = hasMoreRules;
}
+
+ public Map<String, Long> getSeverityDistribution() {
+ return severityDistribution;
+ }
}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/codequalityissue/CodeQualityIssueWorkflowDefinition.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/codequalityissue/CodeQualityIssueWorkflowDefinition.java
index 389d1697c5f..3da617a1be9 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/codequalityissue/CodeQualityIssueWorkflowDefinition.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/codequalityissue/CodeQualityIssueWorkflowDefinition.java
@@ -26,6 +26,7 @@ import org.sonar.api.server.ServerSide;
import org.sonar.issue.workflow.statemachine.StateMachine;
import org.sonar.issue.workflow.statemachine.Transition;
+import static java.util.function.Predicate.not;
import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
import static org.sonar.api.issue.Issue.RESOLUTION_REMOVED;
@@ -56,6 +57,9 @@ public class CodeQualityIssueWorkflowDefinition {
private static final Consumer<CodeQualityIssueWorkflowActions> UNSET_RESOLUTION = CodeQualityIssueWorkflowActions::unsetResolution;
private static final Consumer<CodeQualityIssueWorkflowActions> RESTORE_RESOLUTION = CodeQualityIssueWorkflowActions::restoreResolution;
+ private static final Predicate<CodeQualityIssueWorkflowEntity> AUTOMATIC_REOPEN_PREDICATE = not(CodeQualityIssueWorkflowEntity::isBeingClosed)
+ .and(issue -> issue.hasAnyResolution(RESOLUTION_FIXED));
+
private final StateMachine<CodeQualityIssueWorkflowEntity, CodeQualityIssueWorkflowActions> machine;
public CodeQualityIssueWorkflowDefinition() {
@@ -181,9 +185,7 @@ public class CodeQualityIssueWorkflowDefinition {
// Reopen issues that are marked as resolved but that are still alive.
.transition(Transition.<CodeQualityIssueWorkflowEntity, CodeQualityIssueWorkflowActions>builder("automaticreopen")
.from(STATUS_RESOLVED).to(STATUS_REOPENED)
- .conditions(
- Predicate.not(CodeQualityIssueWorkflowEntity::isBeingClosed),
- i -> i.hasAnyResolution(RESOLUTION_FIXED))
+ .conditions(AUTOMATIC_REOPEN_PREDICATE)
.actions(UNSET_RESOLUTION, UNSET_CLOSE_DATE)
.automatic()
.build())
@@ -227,6 +229,8 @@ public class CodeQualityIssueWorkflowDefinition {
.from(STATUS_RESOLVED)
.to(STATUS_REOPENED)
.conditions(
+ // We check this first condition to avoid overlap with the automaticreopen transition.
+ not(AUTOMATIC_REOPEN_PREDICATE),
CodeQualityIssueWorkflowEntity::isTaintVulnerability,
CodeQualityIssueWorkflowEntity::locationsChanged)
.actions(
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/SecurityStandardCategoryStatisticsTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/SecurityStandardCategoryStatisticsTest.java
index 36638caf954..a23c45cd248 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/SecurityStandardCategoryStatisticsTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/SecurityStandardCategoryStatisticsTest.java
@@ -20,6 +20,7 @@
package org.sonar.server.issue.index;
import java.util.ArrayList;
+import java.util.Map;
import java.util.OptionalInt;
import org.junit.Test;
@@ -33,7 +34,7 @@ public class SecurityStandardCategoryStatisticsTest {
public void hasMoreRules_default_false() {
SecurityStandardCategoryStatistics standardCategoryStatistics = new SecurityStandardCategoryStatistics(
"cat", 0, empty(), 0,
- 0, 5, null, null
+ 0, 5, null, null, Map.of()
);
assertThat(standardCategoryStatistics.hasMoreRules()).isFalse();
}
@@ -42,7 +43,7 @@ public class SecurityStandardCategoryStatisticsTest {
public void hasMoreRules_is_updatable() {
SecurityStandardCategoryStatistics standardCategoryStatistics = new SecurityStandardCategoryStatistics(
"cat", 0, empty(), 0,
- 0, 5, null, null
+ 0, 5, null, null, Map.of()
);
standardCategoryStatistics.setHasMoreRules(true);
assertThat(standardCategoryStatistics.hasMoreRules()).isTrue();
@@ -52,7 +53,7 @@ public class SecurityStandardCategoryStatisticsTest {
public void test_getters() {
SecurityStandardCategoryStatistics standardCategoryStatistics = new SecurityStandardCategoryStatistics(
"cat", 1, empty(), 0,
- 0, 5, new ArrayList<>(), "version"
+ 0, 5, new ArrayList<>(), "version", Map.of()
).setLevel("1");
standardCategoryStatistics.setActiveRules(3);
@@ -71,13 +72,14 @@ public class SecurityStandardCategoryStatisticsTest {
assertThat(standardCategoryStatistics.getVersion().get()).contains("version");
assertThat(standardCategoryStatistics.getLevel().get()).contains("1");
assertThat(standardCategoryStatistics.hasMoreRules()).isFalse();
+ assertThat(standardCategoryStatistics.getSeverityDistribution()).isEmpty();
}
@Test
public void withModifiedVulnerabilities() {
SecurityStandardCategoryStatistics standardCategoryStatistics = new SecurityStandardCategoryStatistics(
"cat", 1, empty(), 0,
- 0, 5, null, null
+ 0, 5, null, null, Map.of()
);
SecurityStandardCategoryStatistics modified = standardCategoryStatistics.withModifiedVulnerabilities(2, 3);
@@ -91,7 +93,7 @@ public class SecurityStandardCategoryStatisticsTest {
public void withModifiedVulnerabilities_noNewRating() {
SecurityStandardCategoryStatistics standardCategoryStatistics = new SecurityStandardCategoryStatistics(
"cat", 1, OptionalInt.of(1), 0,
- 0, 5, null, null
+ 0, 5, null, null, Map.of()
);
SecurityStandardCategoryStatistics modified = standardCategoryStatistics.withModifiedVulnerabilities(2, null);
@@ -105,7 +107,7 @@ public class SecurityStandardCategoryStatisticsTest {
public void withModifiedVulnerabilities_usesLowestRating() {
SecurityStandardCategoryStatistics standardCategoryStatistics = new SecurityStandardCategoryStatistics(
"cat", 1, OptionalInt.of(5), 0,
- 0, 5, null, null
+ 0, 5, null, null, Map.of()
);
SecurityStandardCategoryStatistics modified = standardCategoryStatistics.withModifiedVulnerabilities(2, 3);
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowForCodeQualityIssuesTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowForCodeQualityIssuesTest.java
index 386b3666c26..956ef000a0e 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowForCodeQualityIssuesTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowForCodeQualityIssuesTest.java
@@ -60,6 +60,7 @@ import static org.sonar.api.issue.Issue.STATUS_OPEN;
import static org.sonar.api.issue.Issue.STATUS_REOPENED;
import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByScanBuilder;
+import static org.sonar.core.rule.RuleType.VULNERABILITY;
class IssueWorkflowForCodeQualityIssuesTest {
@@ -455,6 +456,27 @@ class IssueWorkflowForCodeQualityIssuesTest {
assertThat(issue.assignee()).isNull();
}
+ @Test
+ void doManualTransition_shouldUseAutomaticReopenTransitionOnTaintVulnerability_whenMarkedAsResolvedButStillAlive() {
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("issue_key")
+ .setRuleKey(RuleKey.of("xoo", "S001"))
+ .setStatus(STATUS_RESOLVED)
+ .setResolution(RESOLUTION_FIXED)
+ .setLocationsChanged(true)
+ .setNew(false)
+ .setType(VULNERABILITY)
+ .setBeingClosed(false);
+ when(taintChecker.isTaintVulnerability(issue))
+ .thenReturn(true);
+
+ underTest.doAutomaticTransition(issue, issueChangeContextByScanBuilder(new Date()).build());
+
+ assertThat(issue.issueStatus()).isEqualTo(IssueStatus.OPEN);
+ List<DefaultIssueComment> issueComments = issue.defaultIssueComments();
+ assertThat(issueComments).isEmpty();
+ }
+
private static DefaultIssue newClosedIssue(String resolution) {
return new DefaultIssue()
.setKey("ABCDE")
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 fd408c6c26a..9235bc57b1a 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;
@@ -1292,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) {
@@ -1448,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();
@@ -1462,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) {
@@ -1578,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() {
@@ -1594,5 +1606,9 @@ public class IssueIndex {
public OptionalInt getRating() {
return rating;
}
+
+ public Map<String, Long> getDistribution() {
+ return distribution;
+ }
}
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileRepositoryImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileRepositoryImpl.java
index dd20b9c78c8..048487d44ba 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileRepositoryImpl.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileRepositoryImpl.java
@@ -106,7 +106,7 @@ public class BuiltInQProfileRepositoryImpl implements BuiltInQProfileRepository
.collect(Collectors.toSet());
checkState(languagesWithoutBuiltInQProfiles.isEmpty(), "The following languages have no built-in quality profiles: %s",
- languagesWithoutBuiltInQProfiles.isEmpty() ? "" : String.join("", languagesWithoutBuiltInQProfiles));
+ languagesWithoutBuiltInQProfiles.isEmpty() ? "" : String.join(", ", languagesWithoutBuiltInQProfiles));
}
private Map<String, Map<String, BuiltInQualityProfile>> validateAndClean(BuiltInQualityProfilesDefinition.Context context) {
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileRepositoryImplTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileRepositoryImplTest.java
new file mode 100644
index 00000000000..2f76e5f9435
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileRepositoryImplTest.java
@@ -0,0 +1,49 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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.qualityprofile.builtin;
+
+import org.junit.jupiter.api.Test;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.sonar.db.DbClient;
+import org.sonar.server.rule.ServerRuleFinder;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class BuiltInQProfileRepositoryImplTest {
+ @Test
+ void initializationWithoutQualityProfiles() {
+ DbClient dbClient = mock(DbClient.class);
+ ServerRuleFinder ruleFinder = mock(ServerRuleFinder.class);
+ Languages languages = mock(Languages.class);
+ Language java = mock(Language.class);
+ Language kotlin = mock(Language.class);
+
+ when(languages.all()).thenReturn(new Language[]{ java, kotlin });
+ when(java.getKey()).thenReturn("java");
+ when(kotlin.getKey()).thenReturn("kotlin");
+
+ BuiltInQProfileRepositoryImpl repository = new BuiltInQProfileRepositoryImpl(dbClient, ruleFinder, languages);
+
+ assertThatCode(repository::initialize).hasMessage("The following languages have no built-in quality profiles: java, kotlin");
+ }
+}