Browse Source

SONAR-22037 Support new HTTP proxy properties

pull/3360/head
Julien HENRY 4 weeks ago
parent
commit
94d11b4e70

+ 23
- 6
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerWsClientProvider.java View File

@@ -19,6 +19,8 @@
*/
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;
@@ -30,6 +32,7 @@ import org.springframework.context.annotation.Bean;
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;

@@ -57,13 +60,27 @@ public class ScannerWsClientProvider {
.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);
}
}

+ 158
- 19
sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ScannerWsClientProviderTest.java View File

@@ -19,29 +19,85 @@
*/
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/");
@@ -50,23 +106,106 @@ public class ScannerWsClientProviderTest {
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)))));

}
}

Loading…
Cancel
Save