]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22037 Support new HTTP proxy properties
authorJulien HENRY <julien.henry@sonarsource.com>
Fri, 12 Apr 2024 15:48:29 +0000 (17:48 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 15 Apr 2024 20:02:44 +0000 (20:02 +0000)
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerWsClientProvider.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ScannerWsClientProviderTest.java

index b06d0eb4e08d9c5ffa49ee2ed62510f1bae672e8..76f67edbd49de0b091cd2a8718296f5be6d474f7 100644 (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);
   }
 }
index 4fcf8036b48005d17b7f5c71939fa0f1eb01af6f..141a3af7670139c43d84d20f6b896c1293da177a 100644 (file)
  */
 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)))));
+
+  }
 }