aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-scanner-engine/src/test/java/org/sonar/scanner/http/ScannerWsClientProviderTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'sonar-scanner-engine/src/test/java/org/sonar/scanner/http/ScannerWsClientProviderTest.java')
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/http/ScannerWsClientProviderTest.java413
1 files changed, 413 insertions, 0 deletions
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/http/ScannerWsClientProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/http/ScannerWsClientProviderTest.java
new file mode 100644
index 00000000000..e0ee3121acc
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/http/ScannerWsClientProviderTest.java
@@ -0,0 +1,413 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.scanner.http;
+
+import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.io.TempDir;
+import org.junitpioneer.jupiter.RestoreSystemProperties;
+import org.sonar.api.notifications.AnalysisWarnings;
+import org.sonar.api.utils.System2;
+import org.sonar.batch.bootstrapper.EnvironmentInformation;
+import org.sonar.scanner.bootstrap.GlobalAnalysisMode;
+import org.sonar.scanner.bootstrap.ScannerProperties;
+import org.sonar.scanner.bootstrap.SonarUserHome;
+import org.sonarqube.ws.client.GetRequest;
+import org.sonarqube.ws.client.HttpConnector;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;
+import static java.util.Objects.requireNonNull;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class ScannerWsClientProviderTest {
+
+ private static final GlobalAnalysisMode GLOBAL_ANALYSIS_MODE = new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap()));
+ private static final AnalysisWarnings ANALYSIS_WARNINGS = warning -> {
+ };
+ @TempDir
+ private Path sonarUserHomeDir;
+ private final SonarUserHome sonarUserHome = mock(SonarUserHome.class);
+ private final Map<String, String> scannerProps = new HashMap<>();
+
+ private final ScannerWsClientProvider underTest = new ScannerWsClientProvider();
+ private final EnvironmentInformation env = new EnvironmentInformation("Maven Plugin", "2.3");
+
+ private final System2 system2 = mock(System2.class);
+ private final Properties systemProps = new Properties();
+
+ @BeforeEach
+ void configureMocks() {
+ when(system2.properties()).thenReturn(systemProps);
+ when(sonarUserHome.getPath()).thenReturn(sonarUserHomeDir);
+ }
+
+ @Test
+ void provide_client_with_default_settings() {
+ DefaultScannerWsClient client = underTest.provide(new ScannerProperties(scannerProps), env, GLOBAL_ANALYSIS_MODE, system2, ANALYSIS_WARNINGS, sonarUserHome);
+
+ assertThat(client).isNotNull();
+ assertThat(client.baseUrl()).isEqualTo("http://localhost:9000/");
+ HttpConnector httpConnector = (HttpConnector) client.wsConnector();
+ assertThat(httpConnector.baseUrl()).isEqualTo("http://localhost:9000/");
+ assertThat(httpConnector.okHttpClient().proxy()).isNull();
+ assertThat(httpConnector.okHttpClient().connectTimeoutMillis()).isEqualTo(5_000);
+ assertThat(httpConnector.okHttpClient().readTimeoutMillis()).isEqualTo(60_000);
+ }
+
+ @Test
+ void provide_client_with_custom_settings() {
+ scannerProps.put("sonar.host.url", "https://here/sonarqube");
+ scannerProps.put("sonar.token", "testToken");
+ scannerProps.put("sonar.ws.timeout", "42");
+
+ DefaultScannerWsClient client = underTest.provide(new ScannerProperties(scannerProps), env, GLOBAL_ANALYSIS_MODE, system2, ANALYSIS_WARNINGS, sonarUserHome);
+
+ assertThat(client).isNotNull();
+ HttpConnector httpConnector = (HttpConnector) client.wsConnector();
+ assertThat(httpConnector.baseUrl()).isEqualTo("https://here/sonarqube/");
+ assertThat(httpConnector.okHttpClient().proxy()).isNull();
+ }
+
+ @Nested
+ class WithMockHttpSonarQube {
+
+ @RegisterExtension
+ static WireMockExtension sonarqubeMock = WireMockExtension.newInstance()
+ .options(wireMockConfig().dynamicPort())
+ .build();
+
+ @Test
+ void it_should_timeout_on_long_response() {
+ scannerProps.put("sonar.host.url", sonarqubeMock.baseUrl());
+ scannerProps.put("sonar.scanner.responseTimeout", "PT0.2S");
+
+ DefaultScannerWsClient client = underTest.provide(new ScannerProperties(scannerProps), env, GLOBAL_ANALYSIS_MODE, system2, ANALYSIS_WARNINGS, sonarUserHome);
+
+ sonarqubeMock.stubFor(get("/api/plugins/installed")
+ .willReturn(aResponse().withStatus(200)
+ .withFixedDelay(2000)
+ .withBody("Success")));
+
+ HttpConnector httpConnector = (HttpConnector) client.wsConnector();
+
+ var getRequest = new GetRequest("api/plugins/installed");
+ var thrown = assertThrows(IllegalStateException.class, () -> httpConnector.call(getRequest));
+
+ assertThat(thrown).hasStackTraceContaining("timeout");
+ }
+
+ @Test
+ void it_should_timeout_on_slow_response() {
+ scannerProps.put("sonar.host.url", sonarqubeMock.baseUrl());
+ scannerProps.put("sonar.scanner.socketTimeout", "PT0.2S");
+
+ DefaultScannerWsClient client = underTest.provide(new ScannerProperties(scannerProps), env, GLOBAL_ANALYSIS_MODE, system2, ANALYSIS_WARNINGS, sonarUserHome);
+
+ sonarqubeMock.stubFor(get("/api/plugins/installed")
+ .willReturn(aResponse().withStatus(200)
+ .withChunkedDribbleDelay(2, 2000)
+ .withBody("Success")));
+
+ HttpConnector httpConnector = (HttpConnector) client.wsConnector();
+
+ var getRequest = new GetRequest("api/plugins/installed");
+ var thrown = assertThrows(IllegalStateException.class, () -> httpConnector.call(getRequest));
+
+ assertThat(thrown).hasStackTraceContaining("timeout");
+ }
+
+ @Test
+ void it_should_throw_if_invalid_proxy_port() {
+ scannerProps.put("sonar.host.url", sonarqubeMock.baseUrl());
+ scannerProps.put("sonar.scanner.proxyHost", "localhost");
+ scannerProps.put("sonar.scanner.proxyPort", "not_a_number");
+ var scannerPropertiesBean = new ScannerProperties(scannerProps);
+
+ assertThrows(IllegalArgumentException.class, () -> underTest.provide(scannerPropertiesBean, env, GLOBAL_ANALYSIS_MODE, system2, ANALYSIS_WARNINGS, sonarUserHome));
+ }
+
+ @Nested
+ class WithProxy {
+
+ private static final String PROXY_AUTH_ENABLED = "proxy-auth";
+
+ @RegisterExtension
+ static WireMockExtension proxyMock = WireMockExtension.newInstance()
+ .options(wireMockConfig().dynamicPort())
+ .build();
+
+ @BeforeEach
+ void configureMocks(TestInfo info) {
+ if (info.getTags().contains(PROXY_AUTH_ENABLED)) {
+ proxyMock.stubFor(get(urlMatching("/api/plugins/.*"))
+ .inScenario("Proxy Auth")
+ .whenScenarioStateIs(STARTED)
+ .willReturn(aResponse()
+ .withStatus(407)
+ .withHeader("Proxy-Authenticate", "Basic realm=\"Access to the proxy\""))
+ .willSetStateTo("Challenge returned"));
+ proxyMock.stubFor(get(urlMatching("/api/plugins/.*"))
+ .inScenario("Proxy Auth")
+ .whenScenarioStateIs("Challenge returned")
+ .willReturn(aResponse().proxiedFrom(sonarqubeMock.baseUrl())));
+ } else {
+ proxyMock.stubFor(get(urlMatching("/api/plugins/.*")).willReturn(aResponse().proxiedFrom(sonarqubeMock.baseUrl())));
+ }
+ }
+
+ @Test
+ void it_should_honor_scanner_proxy_settings() {
+ scannerProps.put("sonar.host.url", sonarqubeMock.baseUrl());
+ scannerProps.put("sonar.scanner.proxyHost", "localhost");
+ scannerProps.put("sonar.scanner.proxyPort", "" + proxyMock.getPort());
+
+ DefaultScannerWsClient client = underTest.provide(new ScannerProperties(scannerProps), env, GLOBAL_ANALYSIS_MODE, system2, ANALYSIS_WARNINGS, sonarUserHome);
+
+ sonarqubeMock.stubFor(get("/api/plugins/installed")
+ .willReturn(aResponse().withStatus(200).withBody("Success")));
+
+ HttpConnector httpConnector = (HttpConnector) client.wsConnector();
+ try (var r = httpConnector.call(new GetRequest("api/plugins/installed"))) {
+ assertThat(r.code()).isEqualTo(200);
+ assertThat(r.content()).isEqualTo("Success");
+ }
+
+ proxyMock.verify(getRequestedFor(urlEqualTo("/api/plugins/installed")));
+ }
+
+ @Test
+ @Tag(PROXY_AUTH_ENABLED)
+ void it_should_honor_scanner_proxy_settings_with_auth() {
+ var proxyLogin = "proxyLogin";
+ var proxyPassword = "proxyPassword";
+ scannerProps.put("sonar.host.url", sonarqubeMock.baseUrl());
+ scannerProps.put("sonar.scanner.proxyHost", "localhost");
+ scannerProps.put("sonar.scanner.proxyPort", "" + proxyMock.getPort());
+ scannerProps.put("sonar.scanner.proxyUser", proxyLogin);
+ scannerProps.put("sonar.scanner.proxyPassword", proxyPassword);
+
+ DefaultScannerWsClient client = underTest.provide(new ScannerProperties(scannerProps), env, GLOBAL_ANALYSIS_MODE, system2, ANALYSIS_WARNINGS, sonarUserHome);
+
+ sonarqubeMock.stubFor(get("/api/plugins/installed")
+ .willReturn(aResponse().withStatus(200).withBody("Success")));
+
+ HttpConnector httpConnector = (HttpConnector) client.wsConnector();
+ try (var r = httpConnector.call(new GetRequest("api/plugins/installed"))) {
+ assertThat(r.code()).isEqualTo(200);
+ assertThat(r.content()).isEqualTo("Success");
+ }
+
+ proxyMock.verify(getRequestedFor(urlEqualTo("/api/plugins/installed"))
+ .withHeader("Proxy-Authorization", equalTo("Basic " + Base64.getEncoder().encodeToString((proxyLogin + ":" + proxyPassword).getBytes(StandardCharsets.UTF_8)))));
+
+ }
+
+ @Test
+ @Tag(PROXY_AUTH_ENABLED)
+ void it_should_honor_old_jvm_proxy_auth_properties() {
+ var proxyLogin = "proxyLogin";
+ var proxyPassword = "proxyPassword";
+ scannerProps.put("sonar.host.url", sonarqubeMock.baseUrl());
+ scannerProps.put("sonar.scanner.proxyHost", "localhost");
+ scannerProps.put("sonar.scanner.proxyPort", "" + proxyMock.getPort());
+ systemProps.put("http.proxyUser", proxyLogin);
+ systemProps.put("http.proxyPassword", proxyPassword);
+
+ DefaultScannerWsClient client = underTest.provide(new ScannerProperties(scannerProps), env, GLOBAL_ANALYSIS_MODE, system2, ANALYSIS_WARNINGS, sonarUserHome);
+
+ sonarqubeMock.stubFor(get("/api/plugins/installed")
+ .willReturn(aResponse().withStatus(200).withBody("Success")));
+
+ HttpConnector httpConnector = (HttpConnector) client.wsConnector();
+ try (var r = httpConnector.call(new GetRequest("api/plugins/installed"))) {
+ assertThat(r.code()).isEqualTo(200);
+ assertThat(r.content()).isEqualTo("Success");
+ }
+
+ proxyMock.verify(getRequestedFor(urlEqualTo("/api/plugins/installed"))
+ .withHeader("Proxy-Authorization", equalTo("Basic " + Base64.getEncoder().encodeToString((proxyLogin + ":" + proxyPassword).getBytes(StandardCharsets.UTF_8)))));
+
+ }
+ }
+
+ }
+
+ @Nested
+ class WithMockHttpsSonarQube {
+
+ public static final String KEYSTORE_PWD = "pwdServerP12";
+
+ @RegisterExtension
+ static WireMockExtension sonarqubeMock = WireMockExtension.newInstance()
+ .options(wireMockConfig().dynamicHttpsPort().httpDisabled(true)
+ .keystoreType("pkcs12")
+ .keystorePath(toPath(requireNonNull(ScannerWsClientProviderTest.class.getResource("/ssl/server.p12"))).toString())
+ .keystorePassword(KEYSTORE_PWD)
+ .keyManagerPassword(KEYSTORE_PWD))
+ .build();
+
+ @BeforeEach
+ void mockResponse() {
+ sonarqubeMock.stubFor(get("/api/plugins/installed")
+ .willReturn(aResponse().withStatus(200).withBody("Success")));
+ }
+
+ @Test
+ void it_should_not_trust_server_self_signed_certificate_by_default() {
+ scannerProps.put("sonar.host.url", sonarqubeMock.baseUrl());
+
+ DefaultScannerWsClient client = underTest.provide(new ScannerProperties(scannerProps), env, GLOBAL_ANALYSIS_MODE, system2, ANALYSIS_WARNINGS, sonarUserHome);
+
+ HttpConnector httpConnector = (HttpConnector) client.wsConnector();
+ var getRequest = new GetRequest("api/plugins/installed");
+ var thrown = assertThrows(IllegalStateException.class, () -> httpConnector.call(getRequest));
+
+ assertThat(thrown).hasStackTraceContaining("CertificateException");
+ }
+
+ @Test
+ void it_should_trust_server_self_signed_certificate_when_certificate_is_in_truststore() {
+ scannerProps.put("sonar.host.url", sonarqubeMock.baseUrl());
+ scannerProps.put("sonar.scanner.truststorePath", toPath(requireNonNull(ScannerWsClientProviderTest.class.getResource("/ssl/client-truststore.p12"))).toString());
+ scannerProps.put("sonar.scanner.truststorePassword", "pwdClientWithServerCA");
+
+ DefaultScannerWsClient client = underTest.provide(new ScannerProperties(scannerProps), env, GLOBAL_ANALYSIS_MODE, system2, ANALYSIS_WARNINGS, sonarUserHome);
+
+ HttpConnector httpConnector = (HttpConnector) client.wsConnector();
+ try (var r = httpConnector.call(new GetRequest("api/plugins/installed"))) {
+ assertThat(r.code()).isEqualTo(200);
+ assertThat(r.content()).isEqualTo("Success");
+ }
+ }
+ }
+
+ @Nested
+ class WithMockHttpsSonarQubeAndClientCertificates {
+
+ public static final String KEYSTORE_PWD = "pwdServerP12";
+
+ @RegisterExtension
+ static WireMockExtension sonarqubeMock = WireMockExtension.newInstance()
+ .options(wireMockConfig().dynamicHttpsPort().httpDisabled(true)
+ .keystoreType("pkcs12")
+ .keystorePath(toPath(requireNonNull(ScannerWsClientProviderTest.class.getResource("/ssl/server.p12"))).toString())
+ .keystorePassword(KEYSTORE_PWD)
+ .keyManagerPassword(KEYSTORE_PWD)
+ .needClientAuth(true)
+ .trustStoreType("pkcs12")
+ .trustStorePath(toPath(requireNonNull(ScannerWsClientProviderTest.class.getResource("/ssl/server-with-client-ca.p12"))).toString())
+ .trustStorePassword("pwdServerWithClientCA"))
+ .build();
+
+ @BeforeEach
+ void mockResponse() {
+ sonarqubeMock.stubFor(get("/api/plugins/installed")
+ .willReturn(aResponse().withStatus(200).withBody("Success")));
+ }
+
+ @Test
+ void it_should_fail_if_client_certificate_not_provided() {
+ scannerProps.put("sonar.host.url", sonarqubeMock.baseUrl());
+ scannerProps.put("sonar.scanner.truststorePath", toPath(requireNonNull(ScannerWsClientProviderTest.class.getResource("/ssl/client-truststore.p12"))).toString());
+ scannerProps.put("sonar.scanner.truststorePassword", "pwdClientWithServerCA");
+
+ DefaultScannerWsClient client = underTest.provide(new ScannerProperties(scannerProps), env, GLOBAL_ANALYSIS_MODE, system2, ANALYSIS_WARNINGS, sonarUserHome);
+
+ HttpConnector httpConnector = (HttpConnector) client.wsConnector();
+ var getRequest = new GetRequest("api/plugins/installed");
+ var thrown = assertThrows(IllegalStateException.class, () -> httpConnector.call(getRequest));
+
+ assertThat(thrown).satisfiesAnyOf(
+ e -> assertThat(e).hasStackTraceContaining("SSLHandshakeException"),
+ // Exception is flaky because of https://bugs.openjdk.org/browse/JDK-8172163
+ e -> assertThat(e).hasStackTraceContaining("Broken pipe"));
+ }
+
+ @Test
+ void it_should_authenticate_using_certificate_in_keystore() {
+ scannerProps.put("sonar.host.url", sonarqubeMock.baseUrl());
+
+ scannerProps.put("sonar.scanner.truststorePath", toPath(requireNonNull(ScannerWsClientProviderTest.class.getResource("/ssl/client-truststore.p12"))).toString());
+ scannerProps.put("sonar.scanner.truststorePassword", "pwdClientWithServerCA");
+ scannerProps.put("sonar.scanner.keystorePath", toPath(requireNonNull(ScannerWsClientProviderTest.class.getResource("/ssl/client.p12"))).toString());
+ scannerProps.put("sonar.scanner.keystorePassword", "pwdClientCertP12");
+
+ DefaultScannerWsClient client = underTest.provide(new ScannerProperties(scannerProps), env, GLOBAL_ANALYSIS_MODE, system2, ANALYSIS_WARNINGS, sonarUserHome);
+
+ HttpConnector httpConnector = (HttpConnector) client.wsConnector();
+ try (var r = httpConnector.call(new GetRequest("api/plugins/installed"))) {
+ assertThat(r.code()).isEqualTo(200);
+ assertThat(r.content()).isEqualTo("Success");
+ }
+ }
+
+ @RestoreSystemProperties
+ @Test
+ void it_should_support_jvm_system_properties() {
+ scannerProps.put("sonar.host.url", sonarqubeMock.baseUrl());
+ System.setProperty("javax.net.ssl.trustStore", toPath(requireNonNull(ScannerWsClientProviderTest.class.getResource("/ssl/client-truststore.p12"))).toString());
+ System.setProperty("javax.net.ssl.trustStorePassword", "pwdClientWithServerCA");
+ System.setProperty("javax.net.ssl.keyStore", toPath(requireNonNull(ScannerWsClientProviderTest.class.getResource("/ssl/client.p12"))).toString());
+ systemProps.setProperty("javax.net.ssl.keyStore", "any value is fine here");
+ System.setProperty("javax.net.ssl.keyStorePassword", "pwdClientCertP12");
+
+ DefaultScannerWsClient client = underTest.provide(new ScannerProperties(scannerProps), env, GLOBAL_ANALYSIS_MODE, system2, ANALYSIS_WARNINGS, sonarUserHome);
+
+ HttpConnector httpConnector = (HttpConnector) client.wsConnector();
+ try (var r = httpConnector.call(new GetRequest("api/plugins/installed"))) {
+ assertThat(r.code()).isEqualTo(200);
+ assertThat(r.content()).isEqualTo("Success");
+ }
+ }
+ }
+
+ private static Path toPath(URL url) {
+ try {
+ return Paths.get(url.toURI());
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}