*/
package org.sonar.scanner.bootstrap;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
import org.sonar.api.CoreProperties;
import org.sonar.api.notifications.AnalysisWarnings;
import org.sonar.api.utils.System2;
import static java.lang.Integer.parseInt;
import static java.lang.String.valueOf;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.sonar.core.config.ProxyProperties.HTTP_PROXY_PASSWORD;
import static org.sonar.core.config.ProxyProperties.HTTP_PROXY_USER;
.url(url)
.credentials(login, scannerProps.property(CoreProperties.PASSWORD));
- // OkHttp detect 'http.proxyHost' java property, but credentials should be filled
- final String proxyUser = System.getProperty(HTTP_PROXY_USER, "");
- if (!proxyUser.isEmpty()) {
- connectorBuilder.proxyCredentials(proxyUser, System.getProperty(HTTP_PROXY_PASSWORD));
+ // OkHttp detects 'http.proxyHost' java property already, so just focus on sonar properties
+ String proxyHost = defaultIfBlank(scannerProps.property("sonar.scanner.proxyHost"), null);
+ if (proxyHost != null) {
+ int proxyPort;
+ String proxyPortStr = defaultIfBlank(scannerProps.property("sonar.scanner.proxyPort"), url.startsWith("https") ? "443" : "80");
+ try {
+ proxyPort = parseInt(proxyPortStr);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid proxy port: " + proxyPortStr, e);
+ }
+ connectorBuilder.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)));
}
- return new DefaultScannerWsClient(WsClientFactories.getDefault().newClient(connectorBuilder.build()), login != null,
- globalMode, analysisWarnings);
+ var scannerProxyUser = scannerProps.property("sonar.scanner.proxyUser");
+ String proxyUser = scannerProxyUser != null ? scannerProxyUser : system.properties().getProperty(HTTP_PROXY_USER, "");
+ if (isNotBlank(proxyUser)) {
+ var scannerProxyPwd = scannerProps.property("sonar.scanner.proxyPassword");
+ String proxyPassword = scannerProxyPwd != null ? scannerProxyPwd : system.properties().getProperty(HTTP_PROXY_PASSWORD, "");
+ connectorBuilder.proxyCredentials(proxyUser, proxyPassword);
+ }
+
+ return new DefaultScannerWsClient(WsClientFactories.getDefault().newClient(connectorBuilder.build()), login != null, globalMode, analysisWarnings);
}
}
*/
package org.sonar.scanner.bootstrap;
+import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
-import org.junit.Test;
+import java.util.Properties;
+import org.junit.jupiter.api.BeforeEach;
+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.sonar.api.notifications.AnalysisWarnings;
import org.sonar.api.utils.System2;
import org.sonar.batch.bootstrapper.EnvironmentInformation;
+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 org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
-public class ScannerWsClientProviderTest {
+class ScannerWsClientProviderTest {
- private ScannerWsClientProvider underTest = new ScannerWsClientProvider();
- private EnvironmentInformation env = new EnvironmentInformation("Maven Plugin", "2.3");
+ public static final GlobalAnalysisMode GLOBAL_ANALYSIS_MODE = new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap()));
+ public static final AnalysisWarnings ANALYSIS_WARNINGS = warning -> {
+ };
+ private final Map<String, String> scannerProps = new HashMap<>();
- @Test
- public void provide_client_with_default_settings() {
- ScannerProperties settings = new ScannerProperties(new HashMap<>());
+ private final ScannerWsClientProvider underTest = new ScannerWsClientProvider();
+ private final EnvironmentInformation env = new EnvironmentInformation("Maven Plugin", "2.3");
+ public static final String PROXY_AUTH_ENABLED = "proxy-auth";
+
+ @RegisterExtension
+ static WireMockExtension sonarqubeMock = WireMockExtension.newInstance()
+ .options(wireMockConfig().dynamicPort())
+ .build();
+
+ @RegisterExtension
+ static WireMockExtension proxyMock = WireMockExtension.newInstance()
+ .options(wireMockConfig().dynamicPort())
+ .build();
+
+ private final System2 system2 = mock(System2.class);
+ private final Properties systemProps = new Properties();
+
+ @BeforeEach
+ void configureMocks(TestInfo info) {
+ when(system2.properties()).thenReturn(systemProps);
- DefaultScannerWsClient client = underTest.provide(settings, env, new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap())),
- mock(System2.class),warning -> {
- });
+ 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 provide_client_with_default_settings() {
+ DefaultScannerWsClient client = underTest.provide(new ScannerProperties(scannerProps), env, GLOBAL_ANALYSIS_MODE, system2, ANALYSIS_WARNINGS);
assertThat(client).isNotNull();
assertThat(client.baseUrl()).isEqualTo("http://localhost:9000/");
assertThat(httpConnector.okHttpClient().proxy()).isNull();
assertThat(httpConnector.okHttpClient().connectTimeoutMillis()).isEqualTo(5_000);
assertThat(httpConnector.okHttpClient().readTimeoutMillis()).isEqualTo(60_000);
+
+ // Proxy is not accessed
+ assertThat(proxyMock.findAllUnmatchedRequests()).isEmpty();
}
@Test
- public void provide_client_with_custom_settings() {
- Map<String, String> props = new HashMap<>();
- props.put("sonar.host.url", "https://here/sonarqube");
- props.put("sonar.token", "testToken");
- props.put("sonar.ws.timeout", "42");
- ScannerProperties settings = new ScannerProperties(props);
+ 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(settings, env, new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap())),
- mock(System2.class),warning -> {
- });
+ DefaultScannerWsClient client = underTest.provide(new ScannerProperties(scannerProps), env, GLOBAL_ANALYSIS_MODE, system2, ANALYSIS_WARNINGS);
assertThat(client).isNotNull();
HttpConnector httpConnector = (HttpConnector) client.wsConnector();
assertThat(httpConnector.baseUrl()).isEqualTo("https://here/sonarqube/");
assertThat(httpConnector.okHttpClient().proxy()).isNull();
}
+
+ @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);
+
+ 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
+ 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));
+ }
+
+ @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);
+
+ 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);
+
+ 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)))));
+
+ }
}