aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.vscode/settings.json3
-rw-r--r--build.gradle63
-rw-r--r--gradle.properties5
-rw-r--r--server/sonar-db-dao/src/it/java/org/sonar/db/report/RegulatoryReportDaoIT.java2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateFindingDto.java5
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml6
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/report/RegulatoryReportMapper.xml4
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/feature/SonarQubeFeature.java8
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/TaintChecker.java2
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/metric/StandardToMQRMetrics.java9
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/issue/TaintCheckerTest.java14
-rw-r--r--server/sonar-webserver-common/src/it/java/org/sonar/server/common/user/service/UserServiceIT.java46
-rw-r--r--server/sonar-webserver-common/src/main/java/org/sonar/server/common/user/service/UserService.java12
-rw-r--r--server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java2
-rw-r--r--server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java24
-rw-r--r--server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ServerMonitoringMetrics.java15
-rw-r--r--server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ce/RecentTasksDurationTask.java24
-rw-r--r--server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ServerMonitoringMetricsTest.java12
-rw-r--r--server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ce/RecentTasksDurationTaskTest.java16
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/email/config/controller/EmailConfigurationController.java6
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/GithubConfigurationController.java6
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/controller/GitlabConfigurationController.java6
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/controller/GroupController.java10
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/mode/controller/ModeController.java3
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projectbindings/controller/ProjectBindingsController.java3
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/user/controller/UserController.java10
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/feature/ws/ListAction.java2
-rw-r--r--server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java10
-rw-r--r--server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java10
-rw-r--r--sonar-core/src/main/java/org/sonar/core/util/ProcessWrapperFactory.java38
-rw-r--r--sonar-core/src/test/java/org/sonar/core/util/ProcessWrapperFactoryTest.java29
31 files changed, 290 insertions, 115 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 81b53b5bfb6..6b80b53c702 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -31,7 +31,8 @@
"**/node_modules": true,
"**/out": true,
"**/coverage": true,
- "**/target": true
+ "**/target": true,
+ "**/*.java": false
},
"javascript.preferences.importModuleSpecifier": "relative",
"typescript.preferences.importModuleSpecifier": "project-relative",
diff --git a/build.gradle b/build.gradle
index f187321d0ba..99cadc2ec9c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -284,60 +284,62 @@ subprojects {
dependencies {
// bundled plugin list -- keep it alphabetically ordered
dependency 'com.sonarsource.abap:sonar-abap-plugin:3.15.1.6010'
+ dependency 'com.sonarsource.armor:sonar-jasmin-plugin:1.3.0.6541'
dependency 'com.sonarsource.cobol:sonar-cobol-plugin:5.8.1.8428'
- dependency 'com.sonarsource.cpp:sonar-cfamily-dependencies-plugin:6.69.0.86956'
- dependency 'com.sonarsource.cpp:sonar-cfamily-plugin:6.69.0.86956'
+ dependency 'com.sonarsource.cpp:sonar-cfamily-dependencies-plugin:6.70.0.87073'
+ dependency 'com.sonarsource.cpp:sonar-cfamily-plugin:6.70.0.87073'
dependency 'com.sonarsource.dart:sonar-dart-plugin:1.3.0.2614'
dependency 'com.sonarsource.dbd:sonar-dbd-plugin:2.2.0.16530'
dependency 'com.sonarsource.dbd:sonar-dbd-java-frontend-plugin:2.2.0.16530'
dependency 'com.sonarsource.dbd:sonar-dbd-python-frontend-plugin:2.2.0.16530'
- dependency 'com.sonarsource.dotnet:sonar-csharp-enterprise-plugin:10.14.0.120626'
- dependency 'com.sonarsource.dotnet:sonar-vbnet-enterprise-plugin:10.14.0.120626'
- dependency 'com.sonarsource.go:sonar-go-enterprise-plugin:1.25.0.3305'
+ dependency 'com.sonarsource.dotnet:sonar-csharp-enterprise-plugin:10.15.0.120848'
+ dependency 'com.sonarsource.dotnet:sonar-vbnet-enterprise-plugin:10.15.0.120848'
+ dependency 'com.sonarsource.go:sonar-go-enterprise-plugin:1.26.0.3421'
dependency 'com.sonarsource.pli:sonar-pli-plugin:1.16.0.5325'
- dependency 'com.sonarsource.plsql:sonar-plsql-plugin:3.16.1.7377'
+ dependency 'com.sonarsource.plsql:sonar-plsql-plugin:3.17.0.7448'
dependency 'com.sonarsource.plugins.vb:sonar-vb-plugin:2.14.1.5552'
dependency 'com.sonarsource.rpg:sonar-rpg-plugin:3.10.0.5337'
- dependency 'com.sonarsource.security:sonar-security-csharp-frontend-plugin:11.5.0.38524'
- dependency 'com.sonarsource.security:sonar-security-go-frontend-plugin:11.5.0.38524'
- dependency 'com.sonarsource.security:sonar-security-java-frontend-plugin:11.5.0.38524'
- dependency 'com.sonarsource.security:sonar-security-js-frontend-plugin:11.5.0.38524'
- dependency 'com.sonarsource.security:sonar-security-kotlin-frontend-plugin:11.5.0.38524'
- dependency 'com.sonarsource.security:sonar-security-php-frontend-plugin:11.5.0.38524'
- dependency 'com.sonarsource.security:sonar-security-plugin:11.5.0.38524'
- dependency 'com.sonarsource.security:sonar-security-python-frontend-plugin:11.5.0.38524'
+ dependency 'com.sonarsource.security:sonar-security-csharp-frontend-plugin:11.6.0.39346'
+ dependency 'com.sonarsource.security:sonar-security-go-frontend-plugin:11.6.0.39346'
+ dependency 'com.sonarsource.security:sonar-security-java-frontend-plugin:11.6.0.39346'
+ dependency 'com.sonarsource.security:sonar-security-js-frontend-plugin:11.6.0.39346'
+ dependency 'com.sonarsource.security:sonar-security-kotlin-frontend-plugin:11.6.0.39346'
+ dependency 'com.sonarsource.security:sonar-security-php-frontend-plugin:11.6.0.39346'
+ dependency 'com.sonarsource.security:sonar-security-plugin:11.6.0.39346'
+ dependency 'com.sonarsource.security:sonar-security-python-frontend-plugin:11.6.0.39346'
+ dependency 'com.sonarsource.security:sonar-security-vbnet-frontend-plugin:11.6.0.39346'
dependency 'com.sonarsource.slang:sonar-apex-plugin:1.20.0.552'
dependency 'org.sonarsource.slang:sonar-ruby-plugin:1.19.0.471'
dependency 'org.sonarsource.slang:sonar-scala-plugin:1.19.0.484'
dependency 'com.sonarsource.swift:sonar-swift-plugin:4.14.0.8764'
- dependency 'com.sonarsource.tsql:sonar-tsql-plugin:1.14.1.7703'
- dependency 'org.sonarsource.dotnet:sonar-csharp-plugin:10.14.0.120626'
- dependency 'org.sonarsource.dotnet:sonar-vbnet-plugin:10.14.0.120626'
+ dependency 'com.sonarsource.tsql:sonar-tsql-plugin:1.15.0.7898'
+ dependency 'org.sonarsource.dotnet:sonar-csharp-plugin:10.15.0.120848'
+ dependency 'org.sonarsource.dotnet:sonar-vbnet-plugin:10.15.0.120848'
dependency 'org.sonarsource.flex:sonar-flex-plugin:2.14.0.5032'
- dependency 'org.sonarsource.go:sonar-go-plugin:1.25.0.3305'
+ dependency 'org.sonarsource.go:sonar-go-plugin:1.26.0.3421'
dependency 'org.sonarsource.html:sonar-html-plugin:3.19.0.5695'
dependency 'org.sonarsource.jacoco:sonar-jacoco-plugin:1.3.0.1538'
dependency 'org.sonarsource.java:sonar-java-plugin:8.18.0.40025'
dependency 'org.sonarsource.java:sonar-java-symbolic-execution-plugin:8.16.0.131'
- dependency 'org.sonarsource.javascript:sonar-javascript-plugin:11.1.0.33853'
+ dependency 'org.sonarsource.javascript:sonar-javascript-plugin:10.25.0.33900'
dependency 'org.sonarsource.php:sonar-php-plugin:3.46.0.13151'
dependency 'org.sonarsource.plugins.cayc:sonar-cayc-plugin:2.4.0.2018'
- dependency 'org.sonarsource.python:sonar-python-plugin:5.5.0.23291'
+ dependency 'org.sonarsource.python:sonar-python-plugin:5.7.0.24163'
dependency 'org.sonarsource.rust:sonar-rust-plugin:1.0.3.786'
- dependency 'com.sonarsource.python:sonar-python-enterprise-plugin:5.5.0.23291'
+ dependency 'com.sonarsource.python:sonar-python-enterprise-plugin:5.7.0.24163'
dependency 'org.sonarsource.kotlin:sonar-kotlin-plugin:3.3.0.7402'
dependency "org.sonarsource.api.plugin:sonar-plugin-api:$pluginApiVersion"
dependency "org.sonarsource.api.plugin:sonar-plugin-api-test-fixtures:$pluginApiVersion"
dependency 'org.sonarsource.xml:sonar-xml-plugin:2.13.0.5938'
- dependency 'org.sonarsource.iac:sonar-iac-plugin:1.47.0.15287'
- dependency 'com.sonarsource.iac:sonar-iac-enterprise-plugin:1.47.0.15287'
- dependency 'org.sonarsource.text:sonar-text-plugin:2.25.0.7448'
- dependency 'com.sonarsource.text:sonar-text-developer-plugin:2.25.0.7448'
- dependency 'com.sonarsource.text:sonar-text-enterprise-plugin:2.25.0.7448'
+ dependency 'org.sonarsource.iac:sonar-iac-plugin:1.48.0.15768'
+ dependency 'com.sonarsource.iac:sonar-iac-enterprise-plugin:1.48.0.15768'
+ dependency 'org.sonarsource.text:sonar-text-plugin:2.26.0.7517'
+ dependency 'com.sonarsource.text:sonar-text-developer-plugin:2.26.0.7517'
+ dependency 'com.sonarsource.text:sonar-text-enterprise-plugin:2.26.0.7517'
dependency 'com.sonarsource.jcl:sonar-jcl-plugin:1.4.1.1493'
- dependency 'com.sonarsource.architecture:sonar-architecture-plugin:1.11.0.5805'
- dependency 'com.sonarsource.architecture:sonar-architecture-java-frontend-plugin:1.11.0.5805'
- dependency 'com.sonarsource.architecture:sonar-architecture-javascript-frontend-plugin:1.11.0.5805'
+ dependency 'com.sonarsource.architecture:sonar-architecture-plugin:2.0.0.6303'
+ dependency 'com.sonarsource.architecture:sonar-architecture-java-frontend-plugin:2.0.0.6303'
+ dependency 'com.sonarsource.architecture:sonar-architecture-javascript-frontend-plugin:2.0.0.6303'
// Webapp
dependency "org.sonarsource.sonarqube:webapp-assets:$webappVersion"
@@ -519,7 +521,8 @@ subprojects {
dependency('org.sonarsource.orchestrator:sonar-orchestrator-junit5:5.6.2.2625') {
exclude 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml'
}
- dependency 'com.sonarsource.pdfreport:security-report-pdf-generation:2.0.0.232'
+ dependency "com.sonarsource.pdfreport:regulatory-report-pdf-generation:${pdfreportVersion}"
+ dependency "com.sonarsource.pdfreport:security-report-pdf-generation:${pdfreportVersion}"
dependency 'com.sonarsource.fixsuggestions:ai-suggestions-shared:1.0.0.1312'
dependency 'org.sonarsource.update-center:sonar-update-center-common:1.35.0.2835'
dependency 'org.sonarsource.classloader:sonar-classloader:1.1.0.1059'
diff --git a/gradle.properties b/gradle.properties
index ca4b7b170e4..5bfe64dc9ec 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -4,7 +4,8 @@ version=25.8
# 6 months from the release date for non LTA versions
# 18 months from the release date for LTA versions
# No change required for patch versions
-versionEOL=2027-02-01
+versionEOL=2026-04-01
+pdfreportVersion=2.0.0.263
pluginApiVersion=13.0.0.3026
description=Open source platform for continuous inspection of code quality
projectTitle=SonarQube
@@ -15,4 +16,4 @@ elasticSearchServerVersion=8.16.3
projectType=application
artifactoryUrl=https://repox.jfrog.io/repox
jre_release_name=jdk-17.0.13+11
-webappVersion=2025.4.0.22316
+webappVersion=2025.4.0.23179
diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/report/RegulatoryReportDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/report/RegulatoryReportDaoIT.java
index 3b3d9ca49cc..88440d187a1 100644
--- a/server/sonar-db-dao/src/it/java/org/sonar/db/report/RegulatoryReportDaoIT.java
+++ b/server/sonar-db-dao/src/it/java/org/sonar/db/report/RegulatoryReportDaoIT.java
@@ -90,7 +90,7 @@ class RegulatoryReportDaoIT {
List<IssueFindingDto> issues = new ArrayList<>();
underTest.scrollIssues(db.getSession(), PROJECT_UUID, result -> issues.add(result.getResultObject()));
- assertThat(issues).extracting(IssueFindingDto::getKey).containsOnly(issue1.getKey(), issue2.getKey(), issue3.getKey());
+ assertThat(issues).extracting(IssueFindingDto::getKey).containsOnly(issue1.getKey(), issue2.getKey(), issue3.getKey(), issueCodeSmell.getKey());
// check fields
IssueFindingDto issue = issues.stream().filter(i -> i.getKey().equals(issue1.getKey())).findFirst().get();
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateFindingDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateFindingDto.java
index ebefc919835..17e310f3887 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateFindingDto.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateFindingDto.java
@@ -27,6 +27,7 @@ public class QualityGateFindingDto {
private String operator = null;
private String valueType = null;
private String errorThreshold = null;
+ private String qualityGateName = null;
public String getDescription() {
return description;
@@ -52,6 +53,10 @@ public class QualityGateFindingDto {
return errorThreshold;
}
+ public String getQualityGateName() {
+ return qualityGateName;
+ }
+
private String getOperator() {
return operator;
}
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml
index d4dd06bf35d..16df5308d01 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml
@@ -7,6 +7,12 @@
</sql>
<sql id="qualityGateFindingColumns">
+ <!--
+ If a row's columns are all `null`, MyBatis, by default, will return `null`
+ instead of an instantiated object with all its properties set to `null`.
+ This case expression, for the QG name, is designed to preserve that behavior.
+ -->
+ CASE WHEN qgc.operator IS NULL THEN NULL ELSE qg.name END AS qualityGateName,
m.short_name as description,
qgc.operator as operator,
m.val_type as valueType,
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/report/RegulatoryReportMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/report/RegulatoryReportMapper.xml
index 7506b52f060..6281acad179 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/report/RegulatoryReportMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/report/RegulatoryReportMapper.xml
@@ -69,8 +69,8 @@
left outer join issues_impacts ii on i.kee = ii.issue_key
where i.project_uuid=#{branchUuid,jdbcType=VARCHAR}
and i.status !='CLOSED'
- <!--BUG, VULNERABILITY, SECURITY_HOTSPOT -->
- and i.issue_type in (2, 3, 4)
+ <!--CODE_SMELL, BUG, VULNERABILITY, SECURITY_HOTSPOT -->
+ and i.issue_type in (1, 2, 3, 4)
order by i.kee, ic.issue_change_creation_date
</select>
</mapper>
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/feature/SonarQubeFeature.java b/server/sonar-server-common/src/main/java/org/sonar/server/feature/SonarQubeFeature.java
index af149a25f55..7a18916216b 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/feature/SonarQubeFeature.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/feature/SonarQubeFeature.java
@@ -24,4 +24,12 @@ public interface SonarQubeFeature {
String getName();
boolean isAvailable();
+
+ default boolean isEnabled() {
+ return isAvailable();
+ }
+
+ default void setEnabled(boolean enabled) {
+ throw new UnsupportedOperationException("This feature does not support enabling/disabling.");
+ }
}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/TaintChecker.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/TaintChecker.java
index f40ddf46ccd..0ffc6baebed 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/TaintChecker.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/TaintChecker.java
@@ -78,7 +78,7 @@ public class TaintChecker {
private List<String> initializeRepositories() {
List<String> repositories = new ArrayList<>(List.of("gosecurity", "javasecurity", "jssecurity", "kotlinsecurity", "phpsecurity", "pythonsecurity",
- "roslyn.sonaranalyzer.security.cs", "tssecurity"));
+ "roslyn.sonaranalyzer.security.cs", "tssecurity", "vbnetsecurity"));
if (!config.hasKey(EXTRA_TAINT_REPOSITORIES)) {
return repositories;
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/metric/StandardToMQRMetrics.java b/server/sonar-server-common/src/main/java/org/sonar/server/metric/StandardToMQRMetrics.java
index 53d39f62652..6ebf315f6ca 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/metric/StandardToMQRMetrics.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/metric/StandardToMQRMetrics.java
@@ -22,6 +22,7 @@ package org.sonar.server.metric;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import java.util.Optional;
+import java.util.Set;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.core.metric.SoftwareQualitiesMetrics;
@@ -120,6 +121,14 @@ public class StandardToMQRMetrics {
return MQR_TO_STANDARD_MODE_METRICS.containsKey(metricKey);
}
+ public static Set<String> getMQRMetrics() {
+ return STANDARD_TO_MQR_MODE_METRICS.keySet();
+ }
+
+ public static Set<String> getStandardMetrics() {
+ return MQR_TO_STANDARD_MODE_METRICS.keySet();
+ }
+
/**
* Retrieves equivalent metric in the other mode. Return empty if metric has no equivalence
*/
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/TaintCheckerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/TaintCheckerTest.java
index 4cbf7aeeb41..3a2471084ec 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/TaintCheckerTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/TaintCheckerTest.java
@@ -44,7 +44,7 @@ public class TaintCheckerTest {
public void test_getTaintIssuesOnly() {
List<IssueDto> taintIssues = underTest.getTaintIssuesOnly(getIssues());
- assertThat(taintIssues).hasSize(8);
+ assertThat(taintIssues).hasSize(9);
assertThat(taintIssues.get(0).getKey()).isEqualTo("taintIssue1");
assertThat(taintIssues.get(1).getKey()).isEqualTo("taintIssue2");
assertThat(taintIssues.get(2).getKey()).isEqualTo("taintIssue3");
@@ -53,6 +53,7 @@ public class TaintCheckerTest {
assertThat(taintIssues.get(5).getKey()).isEqualTo("taintIssue6");
assertThat(taintIssues.get(6).getKey()).isEqualTo("taintIssue7");
assertThat(taintIssues.get(7).getKey()).isEqualTo("taintIssue8");
+ assertThat(taintIssues.get(8).getKey()).isEqualTo("taintIssue9");
}
@Test
@@ -71,7 +72,7 @@ public class TaintCheckerTest {
Map<Boolean, List<IssueDto>> issuesByTaintStatus = underTest.mapIssuesByTaintStatus(getIssues());
assertThat(issuesByTaintStatus.keySet()).hasSize(2);
- assertThat(issuesByTaintStatus.get(true)).hasSize(8);
+ assertThat(issuesByTaintStatus.get(true)).hasSize(9);
assertThat(issuesByTaintStatus.get(false)).hasSize(3);
assertThat(issuesByTaintStatus.get(true).get(0).getKey()).isEqualTo("taintIssue1");
@@ -91,9 +92,9 @@ public class TaintCheckerTest {
@Test
public void test_getTaintRepositories() {
assertThat(underTest.getTaintRepositories())
- .hasSize(8)
+ .hasSize(9)
.containsExactlyInAnyOrder("gosecurity", "javasecurity", "jssecurity", "kotlinsecurity", "phpsecurity", "pythonsecurity",
- "roslyn.sonaranalyzer.security.cs", "tssecurity");
+ "roslyn.sonaranalyzer.security.cs", "tssecurity", "vbnetsecurity");
}
@Test
@@ -102,9 +103,9 @@ public class TaintCheckerTest {
when(configuration.getStringArray(EXTRA_TAINT_REPOSITORIES)).thenReturn(new String[]{"extra-1", "extra-2"});
TaintChecker underTest = new TaintChecker(configuration);
assertThat(underTest.getTaintRepositories())
- .hasSize(10)
+ .hasSize(11)
.containsExactlyInAnyOrder("gosecurity", "javasecurity", "jssecurity", "kotlinsecurity", "phpsecurity", "pythonsecurity",
- "roslyn.sonaranalyzer.security.cs", "tssecurity", "extra-1", "extra-2");
+ "roslyn.sonaranalyzer.security.cs", "tssecurity", "vbnetsecurity", "extra-1", "extra-2");
}
@Test
@@ -141,6 +142,7 @@ public class TaintCheckerTest {
issues.add(createIssueWithRepository("taintIssue6", "pythonsecurity"));
issues.add(createIssueWithRepository("taintIssue7", "kotlinsecurity"));
issues.add(createIssueWithRepository("taintIssue8", "gosecurity"));
+ issues.add(createIssueWithRepository("taintIssue9", "vbnetsecurity"));
issues.add(createIssueWithRepository("standardIssue1", "java"));
issues.add(createIssueWithRepository("standardIssue2", "python"));
diff --git a/server/sonar-webserver-common/src/it/java/org/sonar/server/common/user/service/UserServiceIT.java b/server/sonar-webserver-common/src/it/java/org/sonar/server/common/user/service/UserServiceIT.java
index d187336783d..f69a9019e4d 100644
--- a/server/sonar-webserver-common/src/it/java/org/sonar/server/common/user/service/UserServiceIT.java
+++ b/server/sonar-webserver-common/src/it/java/org/sonar/server/common/user/service/UserServiceIT.java
@@ -846,6 +846,52 @@ public class UserServiceIT {
assertThat(updatedUser.getExternalLogin()).isEqualTo("prov_login");
}
+ @Test
+ public void updateUser_whenUpdatingScmAccountsAndInstanceManaged_shouldChange() {
+ when(managedInstanceService.isInstanceExternallyManaged()).thenReturn(true);
+
+ UserDto userDto = db.users().insertUser();
+ UpdateUser updateUser = new UpdateUser();
+ updateUser.setScmAccounts(List.of("newaccount"));
+
+ userService.updateUser(userDto.getUuid(), updateUser);
+
+ UserDto updatedUser = db.users().selectUserByLogin(userDto.getLogin()).orElseThrow();
+
+ assertThat(updatedUser.getSortedScmAccounts()).isEqualTo(List.of("newaccount"));
+ }
+
+ @DataProvider
+ public static Object[][] managedInstanceBlockedFields() {
+ return new Object[][] {
+ { "email", (Function<UpdateUser,UpdateUser>) updateUser -> updateUser.setEmail("new@email.com")},
+ { "email", (Function<UpdateUser,UpdateUser>) updateUser -> updateUser.setName("new name")},
+ { "email", (Function<UpdateUser,UpdateUser>) updateUser -> updateUser.setExternalIdentityProviderLogin("new-external-login")},
+ { "email", (Function<UpdateUser,UpdateUser>) updateUser -> updateUser.setExternalIdentityProvider("LDAP")},
+ { "email", (Function<UpdateUser,UpdateUser>) updateUser -> updateUser.setExternalIdentityProviderId("new-provider-id")},
+ };
+ }
+
+ @Test
+ @UseDataProvider("managedInstanceBlockedFields")
+ public void updateUser_whenUpdatingBlockedFieldAndInstanceManaged_shouldThrow(String fieldName,
+ Function<UpdateUser, UpdateUser> updateUserFunction) {
+ doThrow(BadRequestException.create("User information's cannot be updated when the instance is externally managed"))
+ .when(managedInstanceChecker).throwIfInstanceIsManaged(any());
+
+ UserDto userDto = db.users().insertUser();
+
+ assertThatThrownBy(() -> updateUser(userDto, updateUserFunction))
+ .isInstanceOf(BadRequestException.class)
+ .hasMessage("User information's cannot be updated when the instance is externally managed");
+ }
+
+ private void updateUser(UserDto userDto, Function<UpdateUser, UpdateUser> updateUserFunction) {
+ UpdateUser updateUser = new UpdateUser();
+ updateUser = updateUserFunction.apply(updateUser);
+ userService.updateUser(userDto.getUuid(), updateUser);
+ }
+
@DataProvider
public static Object[][] updateUserProvider() {
return new Object[][] {
diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/user/service/UserService.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/user/service/UserService.java
index 95f24094f58..23a78e60169 100644
--- a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/user/service/UserService.java
+++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/user/service/UserService.java
@@ -222,7 +222,7 @@ public class UserService {
public UserInformation updateUser(String uuid, UpdateUser updateUser) {
try (DbSession dbSession = dbClient.openSession(false)) {
throwIfInvalidChangeOfExternalProvider(updateUser);
- throwIfManagedInstanceAndNameOrEmailUpdated(updateUser);
+ throwIfManagedInstanceAndNameOrEmailOrExternalInfoUpdated(updateUser);
UserDto userDto = findUserOrThrow(uuid, dbSession);
userUpdater.updateAndCommit(dbSession, userDto, updateUser, u -> {
});
@@ -248,11 +248,15 @@ public class UserService {
Optional.ofNullable(updateUser.externalIdentityProvider()).ifPresent(this::assertProviderIsSupported);
}
- private void throwIfManagedInstanceAndNameOrEmailUpdated(UpdateUser updateUser) {
+ private void throwIfManagedInstanceAndNameOrEmailOrExternalInfoUpdated(UpdateUser updateUser) {
boolean isNameChanged = updateUser.isNameChanged();
boolean isEmailDefined = updateUser.isEmailChanged();
- if (isNameChanged || isEmailDefined) {
- managedInstanceChecker.throwIfInstanceIsManaged("User name and email cannot be updated when the instance is externally managed");
+ boolean isExternalLoginChanged = updateUser.isExternalIdentityProviderLoginChanged();
+ boolean isExternalProviderChanged = updateUser.isExternalIdentityProviderChanged();
+ boolean isExternalIdChanged = updateUser.isExternalIdentityProviderIdChanged();
+
+ if (isNameChanged || isEmailDefined || isExternalLoginChanged || isExternalProviderChanged || isExternalIdChanged) {
+ managedInstanceChecker.throwIfInstanceIsManaged("User information's cannot be updated when the instance is externally managed");
}
}
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 9235bc57b1a..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
@@ -871,7 +871,7 @@ 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));
+ termQuery(FIELD_ISSUE_NEW_CODE_REFERENCE, newCodeOnReference));
}
}
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 a8c4216b14f..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
@@ -269,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);
@@ -301,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) {
diff --git a/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ServerMonitoringMetrics.java b/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ServerMonitoringMetrics.java
index 9d6c9c9ebc5..39f91037d8c 100644
--- a/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ServerMonitoringMetrics.java
+++ b/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ServerMonitoringMetrics.java
@@ -37,6 +37,7 @@ public class ServerMonitoringMetrics {
private final Gauge cePendingTasksTotal;
private final Summary ceTasksRunningDuration;
+ private final Summary ceSystemTasksRunningDuration;
private final Gauge elasticsearchDiskSpaceFreeBytesGauge;
private final Gauge elasticSearchDiskSpaceTotalBytes;
@@ -80,6 +81,12 @@ public class ServerMonitoringMetrics {
.labelNames("task_type", "project_key")
.register();
+ ceSystemTasksRunningDuration = Summary.build()
+ .name("sonarqube_compute_engine_system_tasks_running_duration_seconds")
+ .help("Compute engine system task running time in seconds")
+ .labelNames("task_type")
+ .register();
+
computeEngineGauge = Gauge.build()
.name("sonarqube_health_compute_engine_status")
.help("Tells whether Compute Engine is up (healthy, ready to take tasks) or down. 1 for up, 0 for down")
@@ -165,8 +172,12 @@ public class ServerMonitoringMetrics {
cePendingTasksTotal.set(numberOfPendingTasks);
}
- public void observeComputeEngineTaskDuration(long durationInSeconds, String taskType, String projectKey) {
- ceTasksRunningDuration.labels(taskType, projectKey).observe(durationInSeconds);
+ public void observeComputeEngineTaskDuration(long durationInSeconds, String taskType, String label) {
+ ceTasksRunningDuration.labels(taskType, label).observe(durationInSeconds);
+ }
+
+ public void observeComputeEngineSystemTaskDuration(long durationInSeconds, String taskType) {
+ ceSystemTasksRunningDuration.labels(taskType).observe(durationInSeconds);
}
public void setComputeEngineStatusToGreen() {
diff --git a/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ce/RecentTasksDurationTask.java b/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ce/RecentTasksDurationTask.java
index 74d3dd85387..5a4c22d15d8 100644
--- a/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ce/RecentTasksDurationTask.java
+++ b/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ce/RecentTasksDurationTask.java
@@ -22,11 +22,12 @@ package org.sonar.server.monitoring.ce;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.stream.Collectors;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.utils.System2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.utils.System2;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.ce.CeActivityDto;
@@ -56,6 +57,7 @@ public class RecentTasksDurationTask extends ComputeEngineMetricsTask {
Collection<String> entityUuids = recentSuccessfulTasks.stream()
.map(CeActivityDto::getEntityUuid)
+ .filter(Objects::nonNull)
.toList();
List<EntityDto> entities = dbClient.entityDao().selectByUuids(dbSession, entityUuids);
Map<String, String> entityUuidAndKeys = entities.stream()
@@ -75,20 +77,22 @@ public class RecentTasksDurationTask extends ComputeEngineMetricsTask {
private void reportObservedDurationForTasks(List<CeActivityDto> tasks, Map<String, String> entityUuidAndKeys) {
for (CeActivityDto task : tasks) {
- String mainComponentUuid = task.getEntityUuid();
+ String entityUuid = task.getEntityUuid();
Long executionTimeMs = task.getExecutionTimeMs();
try {
- requireNonNull(mainComponentUuid);
requireNonNull(executionTimeMs);
- String mainComponentKey = entityUuidAndKeys.get(mainComponentUuid);
- requireNonNull(mainComponentKey);
-
- metrics.observeComputeEngineTaskDuration(executionTimeMs, task.getTaskType(), mainComponentKey);
+ if (entityUuid != null) {
+ String label = entityUuidAndKeys.get(entityUuid);
+ requireNonNull(label);
+ metrics.observeComputeEngineTaskDuration(executionTimeMs, task.getTaskType(), label);
+ } else {
+ metrics.observeComputeEngineSystemTaskDuration(executionTimeMs, task.getTaskType());
+ }
} catch (RuntimeException e) {
- LOGGER.warn("Can't report metric data for a CE task with component uuid " + mainComponentUuid, e);
+ LOGGER.warn("Can't report metric data for a CE task with entity uuid " + entityUuid, e);
}
}
-
}
+
}
diff --git a/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ServerMonitoringMetricsTest.java b/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ServerMonitoringMetricsTest.java
index 8b0cbd624f0..ecacceefccb 100644
--- a/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ServerMonitoringMetricsTest.java
+++ b/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ServerMonitoringMetricsTest.java
@@ -158,6 +158,18 @@ public class ServerMonitoringMetricsTest {
labelNames, labelValues)).isEqualTo(10);
}
+ @Test
+ public void observeComputeEngineSystemTaskDurationTest() {
+ ServerMonitoringMetrics metrics = new ServerMonitoringMetrics();
+ String[] labelNames = {"task_type"};
+ String[] labelValues = {"AUDIT_PURGE"};
+
+ metrics.observeComputeEngineSystemTaskDuration(10, labelValues[0]);
+
+ assertThat(CollectorRegistry.defaultRegistry.getSampleValue("sonarqube_compute_engine_system_tasks_running_duration_seconds_sum",
+ labelNames, labelValues)).isEqualTo(10);
+ }
+
private int sizeOfDefaultRegistry() {
Enumeration<Collector.MetricFamilySamples> metrics = CollectorRegistry.defaultRegistry.metricFamilySamples();
return Collections.list(metrics).size();
diff --git a/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ce/RecentTasksDurationTaskTest.java b/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ce/RecentTasksDurationTaskTest.java
index 5b5fc0a3570..7fdbfaa9dee 100644
--- a/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ce/RecentTasksDurationTaskTest.java
+++ b/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ce/RecentTasksDurationTaskTest.java
@@ -86,6 +86,22 @@ public class RecentTasksDurationTaskTest {
}
@Test
+ public void run_given1TaskWithEntityUuidAnd1Without_observeDurationFor2Tasks() {
+ RecentTasksDurationTask task = new RecentTasksDurationTask(dbClient, metrics, config, system);
+ List<CeActivityDto> recentTasks = createTasks(2, 0);
+
+ recentTasks.get(0).setEntityUuid(null);
+
+ when(entityDao.selectByUuids(any(), any())).thenReturn(createEntityDtos(1));
+ when(ceActivityDao.selectNewerThan(any(), anyLong())).thenReturn(recentTasks);
+
+ task.run();
+
+ verify(metrics, times(1)).observeComputeEngineTaskDuration(anyLong(), any(), any());
+ verify(metrics, times(1)).observeComputeEngineSystemTaskDuration(anyLong(), any());
+ }
+
+ @Test
public void run_givenNullExecutionTime_dontReportMetricData() {
RecentTasksDurationTask task = new RecentTasksDurationTask(dbClient, metrics, config, system);
List<CeActivityDto> recentTasks = createTasks(1, 0);
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/email/config/controller/EmailConfigurationController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/email/config/controller/EmailConfigurationController.java
index a62ec04c33a..8fb0f5e05a3 100644
--- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/email/config/controller/EmailConfigurationController.java
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/email/config/controller/EmailConfigurationController.java
@@ -30,7 +30,6 @@ import org.sonar.server.v2.api.email.config.request.EmailConfigurationUpdateRest
import org.sonar.server.v2.api.email.config.resource.EmailConfigurationResource;
import org.sonar.server.v2.api.email.config.response.EmailConfigurationSearchRestResponse;
import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
@@ -44,6 +43,7 @@ import org.springframework.web.bind.annotation.RestController;
import static org.sonar.server.v2.WebApiEndpoints.EMAIL_CONFIGURATION_ENDPOINT;
import static org.sonar.server.v2.WebApiEndpoints.INTERNAL;
import static org.sonar.server.v2.WebApiEndpoints.JSON_MERGE_PATCH_CONTENT_TYPE;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@RequestMapping(EMAIL_CONFIGURATION_ENDPOINT)
@RestController
@@ -58,7 +58,7 @@ public interface EmailConfigurationController {
extensions = @Extension(properties = {@ExtensionProperty(name = INTERNAL, value = "true")}))
EmailConfigurationResource createEmailConfiguration(@Valid @RequestBody EmailConfigurationCreateRestRequest createRequest);
- @GetMapping(path = "/{id}")
+ @GetMapping(path = "/{id}", produces = APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Fetch an email configuration", description = """
Fetch a Email configuration. Requires 'Administer System' permission.
@@ -76,7 +76,7 @@ public interface EmailConfigurationController {
extensions = @Extension(properties = {@ExtensionProperty(name = INTERNAL, value = "true")}))
EmailConfigurationSearchRestResponse searchEmailConfigurations();
- @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = MediaType.APPLICATION_JSON_VALUE)
+ @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Update an email configuration", description = """
Update an email configuration. Requires 'Administer System' permission.
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/GithubConfigurationController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/GithubConfigurationController.java
index dda8fdbc4b7..d246cb4af26 100644
--- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/GithubConfigurationController.java
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/GithubConfigurationController.java
@@ -30,7 +30,6 @@ import org.sonar.server.v2.api.github.config.request.GithubConfigurationUpdateRe
import org.sonar.server.v2.api.github.config.resource.GithubConfigurationResource;
import org.sonar.server.v2.api.github.config.response.GithubConfigurationSearchRestResponse;
import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
@@ -44,12 +43,13 @@ import org.springframework.web.bind.annotation.RestController;
import static org.sonar.server.v2.WebApiEndpoints.GITHUB_CONFIGURATION_ENDPOINT;
import static org.sonar.server.v2.WebApiEndpoints.INTERNAL;
import static org.sonar.server.v2.WebApiEndpoints.JSON_MERGE_PATCH_CONTENT_TYPE;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@RequestMapping(GITHUB_CONFIGURATION_ENDPOINT)
@RestController
public interface GithubConfigurationController {
- @GetMapping(path = "/{id}")
+ @GetMapping(path = "/{id}", produces = APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Fetch a GitHub configuration", description = """
Fetch a GitHub configuration. Requires 'Administer System' permission.
@@ -67,7 +67,7 @@ public interface GithubConfigurationController {
extensions = @Extension(properties = {@ExtensionProperty(name = INTERNAL, value = "true")}))
GithubConfigurationSearchRestResponse searchGithubConfiguration();
- @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = MediaType.APPLICATION_JSON_VALUE)
+ @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Update a GitHub configuration", description = """
Update a GitHub configuration. Requires 'Administer System' permission.
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/controller/GitlabConfigurationController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/controller/GitlabConfigurationController.java
index a00f7cd0891..b3ebde2230f 100644
--- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/controller/GitlabConfigurationController.java
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/controller/GitlabConfigurationController.java
@@ -30,7 +30,6 @@ import org.sonar.server.v2.api.gitlab.config.request.GitlabConfigurationUpdateRe
import org.sonar.server.v2.api.gitlab.config.resource.GitlabConfigurationResource;
import org.sonar.server.v2.api.gitlab.config.response.GitlabConfigurationSearchRestResponse;
import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
@@ -44,12 +43,13 @@ import org.springframework.web.bind.annotation.RestController;
import static org.sonar.server.v2.WebApiEndpoints.GITLAB_CONFIGURATION_ENDPOINT;
import static org.sonar.server.v2.WebApiEndpoints.INTERNAL;
import static org.sonar.server.v2.WebApiEndpoints.JSON_MERGE_PATCH_CONTENT_TYPE;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@RequestMapping(GITLAB_CONFIGURATION_ENDPOINT)
@RestController
public interface GitlabConfigurationController {
- @GetMapping(path = "/{id}")
+ @GetMapping(path = "/{id}", produces = APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Fetch a GitLab configuration", description = """
Fetch a GitLab configuration. Requires 'Administer System' permission.
@@ -67,7 +67,7 @@ public interface GitlabConfigurationController {
extensions = @Extension(properties = {@ExtensionProperty(name = INTERNAL, value = "true")}))
GitlabConfigurationSearchRestResponse searchGitlabConfiguration();
- @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = MediaType.APPLICATION_JSON_VALUE)
+ @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Update a Gitlab configuration", description = """
Update a Gitlab configuration. Requires 'Administer System' permission.
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/controller/GroupController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/controller/GroupController.java
index f6f6a149679..66bda515c44 100644
--- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/controller/GroupController.java
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/controller/GroupController.java
@@ -35,7 +35,6 @@ import org.sonar.server.v2.api.group.response.GroupsSearchRestResponse;
import org.sonar.server.v2.api.model.RestPage;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
@@ -49,12 +48,13 @@ import org.springframework.web.bind.annotation.RestController;
import static org.sonar.server.v2.WebApiEndpoints.GROUPS_ENDPOINT;
import static org.sonar.server.v2.WebApiEndpoints.JSON_MERGE_PATCH_CONTENT_TYPE;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@RequestMapping(GROUPS_ENDPOINT)
@RestController
public interface GroupController {
- @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+ @GetMapping(produces = APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Group search", description = """
Get the list of groups.
@@ -66,12 +66,12 @@ public interface GroupController {
extensions = @Extension(properties = {@ExtensionProperty(name = "internal", value = "true")}), hidden = true) String excludedUserId,
@Valid @ParameterObject RestPage restPage);
- @GetMapping(path = "/{id}")
+ @GetMapping(path = "/{id}", produces = APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Fetch a single group", description = "Fetch a single group.")
GroupRestResponse fetchGroup(@PathVariable("id") @Parameter(description = "The id of the group to fetch.", required = true, in = ParameterIn.PATH) String id);
- @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
+ @PostMapping(consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.CREATED)
@Operation(summary = "Create a new group", description = "Create a new group.")
GroupRestResponse create(@Valid @RequestBody GroupCreateRestRequest request);
@@ -81,7 +81,7 @@ public interface GroupController {
@Operation(summary = "Deletes a group", description = "Deletes a group.")
void deleteGroup(@PathVariable("id") @Parameter(description = "The ID of the group to delete.", required = true, in = ParameterIn.PATH) String id);
- @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = MediaType.APPLICATION_JSON_VALUE)
+ @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Update a group", description = """
Update a group name or description.
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/mode/controller/ModeController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/mode/controller/ModeController.java
index 336584a778a..3dec8655db4 100644
--- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/mode/controller/ModeController.java
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/mode/controller/ModeController.java
@@ -34,12 +34,13 @@ import org.springframework.web.bind.annotation.RestController;
import static org.sonar.server.v2.WebApiEndpoints.INTERNAL;
import static org.sonar.server.v2.WebApiEndpoints.MODE_ENDPOINT;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@RequestMapping(MODE_ENDPOINT)
@RestController
public interface ModeController {
- @GetMapping(path = "")
+ @GetMapping(path = "", produces = APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Retrieve current instance Mode", description = """
Fetch the current instance mode. Can be Multi-Quality Rules (MQR) Mode or Standard Experience.
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projectbindings/controller/ProjectBindingsController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projectbindings/controller/ProjectBindingsController.java
index 64d3eda84b0..8a7ca41d895 100644
--- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projectbindings/controller/ProjectBindingsController.java
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projectbindings/controller/ProjectBindingsController.java
@@ -37,12 +37,13 @@ import org.springframework.web.bind.annotation.RestController;
import static org.sonar.server.v2.WebApiEndpoints.INTERNAL;
import static org.sonar.server.v2.WebApiEndpoints.PROJECT_BINDINGS_ENDPOINT;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@RequestMapping(PROJECT_BINDINGS_ENDPOINT)
@RestController
public interface ProjectBindingsController {
- @GetMapping(path = "/{id}")
+ @GetMapping(path = "/{id}", produces = APPLICATION_JSON_VALUE)
@Operation(
operationId = "getProjectBinding",
summary = "Fetch a single Project Binding",
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/user/controller/UserController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/user/controller/UserController.java
index 52563f6d997..1e478f0b602 100644
--- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/user/controller/UserController.java
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/user/controller/UserController.java
@@ -35,7 +35,6 @@ import org.sonar.server.v2.api.user.response.UserRestResponse;
import org.sonar.server.v2.api.user.response.UsersSearchRestResponse;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
@@ -49,12 +48,13 @@ import org.springframework.web.bind.annotation.RestController;
import static org.sonar.server.v2.WebApiEndpoints.JSON_MERGE_PATCH_CONTENT_TYPE;
import static org.sonar.server.v2.WebApiEndpoints.USER_ENDPOINT;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@RequestMapping(USER_ENDPOINT)
@RestController
public interface UserController {
- @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+ @GetMapping(produces = APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Users search", description = """
Get a list of users. By default, only active users are returned.
@@ -82,7 +82,7 @@ public interface UserController {
@PathVariable("id") @Parameter(description = "The ID of the user to delete.", required = true, in = ParameterIn.PATH) String id,
@RequestParam(value = "anonymize", required = false, defaultValue = "false") @Parameter(description = "Anonymize user in addition to deactivating it.") Boolean anonymize);
- @GetMapping(path = "/{id}")
+ @GetMapping(path = "/{id}", produces = APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Fetch a single user", description = """
Fetch a single user.
@@ -98,14 +98,14 @@ public interface UserController {
""")
UserRestResponse fetchUser(@PathVariable("id") @Parameter(description = "The id of the user to fetch.", required = true, in = ParameterIn.PATH) String id);
- @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = MediaType.APPLICATION_JSON_VALUE)
+ @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Update a user", description = """
Update users attributes.
""")
UserRestResponse updateUser(@PathVariable("id") String id, @Valid @RequestBody UserUpdateRestRequest updateRequest);
- @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
+ @PostMapping(consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "User creation", description = """
Create a user.
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/feature/ws/ListAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/feature/ws/ListAction.java
index 0d4219e0805..a7db09fc823 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/feature/ws/ListAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/feature/ws/ListAction.java
@@ -51,7 +51,7 @@ public class ListAction implements WsAction {
try (JsonWriter json = response.newJsonWriter()) {
json.beginArray();
sonarQubeFeatures.stream()
- .filter(SonarQubeFeature::isAvailable)
+ .filter(SonarQubeFeature::isEnabled)
.forEach(f -> json.value(f.getName()));
json.endArray();
}
diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java
index 7015b1a0b46..195aa01c6bf 100644
--- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java
+++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java
@@ -35,8 +35,6 @@ import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletResponse;
public class CspFilter implements Filter {
- private static final String BEAMER_SRC = " https://*.getbeamer.com ";
-
private final List<String> cspHeaders = new ArrayList<>();
private String policies = null;
@@ -48,13 +46,13 @@ public class CspFilter implements Filter {
cspPolicies.add("default-src 'self'");
cspPolicies.add("base-uri 'none'");
cspPolicies.add("connect-src 'self' http: https:");
- cspPolicies.add("font-src 'self' data:" + BEAMER_SRC);
- cspPolicies.add("frame-src" + BEAMER_SRC);
+ cspPolicies.add("font-src 'self' data:");
+ cspPolicies.add("frame-src");
cspPolicies.add("img-src * data: blob:");
cspPolicies.add("object-src 'none'");
// the hash below corresponds to the window.__assetsPath script in index.html
- cspPolicies.add("script-src 'self'" + BEAMER_SRC + getAssetsPathScriptCSPHash(filterConfig.getServletContext().getContextPath()));
- cspPolicies.add("style-src 'self' 'unsafe-inline'" + BEAMER_SRC);
+ cspPolicies.add("script-src 'self' " + getAssetsPathScriptCSPHash(filterConfig.getServletContext().getContextPath()));
+ cspPolicies.add("style-src 'self' 'unsafe-inline'");
cspPolicies.add("worker-src 'self'");
this.policies = String.join("; ", cspPolicies).trim();
}
diff --git a/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java b/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java
index b2ef0146d4e..c4e0b3cfcd4 100644
--- a/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java
+++ b/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java
@@ -41,12 +41,12 @@ public class CspFilterTest {
private static final String EXPECTED = "default-src 'self'; " +
"base-uri 'none'; " +
"connect-src 'self' http: https:; " +
- "font-src 'self' data: https://*.getbeamer.com ; " +
- "frame-src https://*.getbeamer.com ; " +
+ "font-src 'self' data:; " +
+ "frame-src; " +
"img-src * data: blob:; " +
"object-src 'none'; " +
- "script-src 'self' https://*.getbeamer.com 'sha256-hK8SVWFNHY0UhP61DBzX/3fvT74EI8u6/jRQvUKeZoU='; " +
- "style-src 'self' 'unsafe-inline' https://*.getbeamer.com ; " +
+ "script-src 'self' 'sha256-hK8SVWFNHY0UhP61DBzX/3fvT74EI8u6/jRQvUKeZoU='; " +
+ "style-src 'self' 'unsafe-inline'; " +
"worker-src 'self'";
private final ServletContext servletContext = mock(ServletContext.class, RETURNS_MOCKS);
private final HttpServletResponse response = mock(HttpServletResponse.class);
@@ -75,7 +75,7 @@ public class CspFilterTest {
doInit();
HttpServletRequest request = newRequest("/");
underTest.doFilter(request, response, chain);
- verify(response).setHeader(eq("Content-Security-Policy"), contains("script-src 'self' https://*.getbeamer.com 'sha256-D1jaqcDDM2TM2STrzE42NNqyKR9PlptcHDe6tyaBcuM='; "));
+ verify(response).setHeader(eq("Content-Security-Policy"), contains("script-src 'self' 'sha256-D1jaqcDDM2TM2STrzE42NNqyKR9PlptcHDe6tyaBcuM='; "));
verify(chain).doFilter(request, response);
}
diff --git a/sonar-core/src/main/java/org/sonar/core/util/ProcessWrapperFactory.java b/sonar-core/src/main/java/org/sonar/core/util/ProcessWrapperFactory.java
index 12abd1396c0..d68ebbc01f1 100644
--- a/sonar-core/src/main/java/org/sonar/core/util/ProcessWrapperFactory.java
+++ b/sonar-core/src/main/java/org/sonar/core/util/ProcessWrapperFactory.java
@@ -23,6 +23,7 @@ import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import org.apache.commons.exec.CommandLine;
@@ -62,6 +63,7 @@ public class ProcessWrapperFactory {
private final Consumer<String> stdErrLineConsumer;
private final String[] command;
private final Map<String, String> envVariables = new HashMap<>();
+ private final AtomicReference<Exception> exceptionWhileProcessingStream = new AtomicReference<>();
private ExecuteWatchdog watchdog = null;
ProcessWrapper(@Nullable Path baseDir, Consumer<String> stdOutLineConsumer, Consumer<String> stdErrLineConsumer, Map<String, String> envVariablesOverrides, String... command) {
@@ -93,6 +95,9 @@ public class ProcessWrapperFactory {
if (exitValue != 0 && !watchdog.killedProcess()) {
throw new IllegalStateException(format("Command execution exited with code: %d", exitValue), resultHandler.getException());
}
+ if (exceptionWhileProcessingStream.get() != null) {
+ throw new IllegalStateException("Error while processing stream for command", exceptionWhileProcessingStream.get());
+ }
} catch (InterruptedException e) {
LOG.warn("Command [{}] interrupted", join(" ", command), e);
Thread.currentThread().interrupt();
@@ -105,17 +110,9 @@ public class ProcessWrapperFactory {
builder.setWorkingDirectory(baseDir.toFile());
}
- PumpStreamHandler psh = new PumpStreamHandler(new LogOutputStream() {
- @Override
- protected void processLine(String line, int logLevel) {
- stdOutLineConsumer.accept(line);
- }
- }, new LogOutputStream() {
- @Override
- protected void processLine(String line, int logLevel) {
- stdErrLineConsumer.accept(line);
- }
- });
+ PumpStreamHandler psh = new PumpStreamHandler(
+ new ExceptionCatchingLogOutputStream(stdOutLineConsumer),
+ new ExceptionCatchingLogOutputStream(stdErrLineConsumer));
builder.setExecuteStreamHandler(psh);
var executor = builder.get();
@@ -127,5 +124,24 @@ public class ProcessWrapperFactory {
public void destroy() {
watchdog.destroyProcess();
}
+
+ private class ExceptionCatchingLogOutputStream extends LogOutputStream {
+
+ private final Consumer<String> lineConsumer;
+
+ public ExceptionCatchingLogOutputStream(Consumer<String> lineConsumer) {
+ this.lineConsumer = lineConsumer;
+ }
+
+ @Override
+ protected void processLine(String line, int logLevel) {
+ try {
+ lineConsumer.accept(line);
+ } catch (Exception e) {
+ exceptionWhileProcessingStream.compareAndSet(null, e);
+ watchdog.destroyProcess();
+ }
+ }
+ }
}
}
diff --git a/sonar-core/src/test/java/org/sonar/core/util/ProcessWrapperFactoryTest.java b/sonar-core/src/test/java/org/sonar/core/util/ProcessWrapperFactoryTest.java
index 1fc0a4205e2..04627cd808c 100644
--- a/sonar-core/src/test/java/org/sonar/core/util/ProcessWrapperFactoryTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/util/ProcessWrapperFactoryTest.java
@@ -83,6 +83,26 @@ class ProcessWrapperFactoryTest {
}
@Test
+ void should_not_deadlock_when_stream_handler_throw_exception(@TempDir Path temp) throws IOException {
+ var bigFile = temp.resolve("stdout.txt");
+ for (int i = 0; i < 1024; i++) {
+ Files.writeString(bigFile, StringUtils.repeat("a", 1024), StandardCharsets.UTF_8, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
+ Files.writeString(bigFile, "\n", StandardCharsets.UTF_8, StandardOpenOption.APPEND);
+ }
+
+ var stdoutHandler = new ThrowExceptionForEveryLine();
+
+ var processWrapper = underTest.create(temp, stdoutHandler::process, l -> {
+ },
+ SystemUtils.IS_OS_WINDOWS ? new String[] {"cmd.exe", "/c", "type stdout.txt"} : new String[] {"cat", "stdout.txt"});
+
+ assertThatThrownBy(processWrapper::execute)
+ .hasMessage("Error while processing stream for command")
+ .hasCauseInstanceOf(IllegalStateException.class)
+ .hasStackTraceContaining("Some error");
+ }
+
+ @Test
void should_apply_env_overrides_on_top_of_parent_env(@TempDir Path temp) throws IOException {
ConcurrentLinkedDeque<String> logs = new ConcurrentLinkedDeque<>();
@@ -99,8 +119,8 @@ class ProcessWrapperFactoryTest {
}
private static class DestroyProcessAfter10Lines {
- private ProcessWrapperFactory.ProcessWrapper wrapper;
private final AtomicInteger lineCounter = new AtomicInteger();
+ private ProcessWrapperFactory.ProcessWrapper wrapper;
void process(String line) {
if (lineCounter.incrementAndGet() == 10) {
@@ -109,4 +129,11 @@ class ProcessWrapperFactoryTest {
}
}
+ private static class ThrowExceptionForEveryLine {
+
+ void process(String line) {
+ throw new IllegalStateException("Some error");
+ }
+ }
+
}