diff --git a/lib/src/main/java/org/sonarsource/scanner/lib/internal/http/ScannerHttpClient.java b/lib/src/main/java/org/sonarsource/scanner/lib/internal/http/ScannerHttpClient.java index 611bb2a6..197e3c9a 100644 --- a/lib/src/main/java/org/sonarsource/scanner/lib/internal/http/ScannerHttpClient.java +++ b/lib/src/main/java/org/sonarsource/scanner/lib/internal/http/ScannerHttpClient.java @@ -43,12 +43,12 @@ public class ScannerHttpClient { private static final String EXCEPTION_MESSAGE_MISSING_SLASH = "URL path must start with slash: %s"; - private OkHttpClient httpClient; + private OkHttpClient sharedHttpClient; private HttpConfig httpConfig; public void init(HttpConfig httpConfig) { this.httpConfig = httpConfig; - this.httpClient = OkHttpClientFactory.create(httpConfig); + this.sharedHttpClient = OkHttpClientFactory.create(httpConfig); } @@ -82,9 +82,6 @@ public void downloadFromExternalUrl(String url, Path toFile) throws IOException * @throws IllegalStateException if HTTP response code is different than 2xx */ private void downloadFile(String url, Path toFile, boolean authentication) throws IOException { - if (httpClient == null) { - throw new IllegalStateException("ServerConnection must be initialized"); - } LOG.debug("Download {} to {}", url, toFile.toAbsolutePath()); try (ResponseBody responseBody = callUrl(url, authentication, "application/octet-stream"); @@ -120,9 +117,6 @@ public String callWebApi(String urlPath) throws IOException { * @throws IllegalStateException if HTTP response code is different than 2xx */ private String callApi(String url) throws IOException { - if (httpClient == null) { - throw new IllegalStateException("ServerConnection must be initialized"); - } try (ResponseBody responseBody = callUrl(url, true, null)) { return responseBody.string(); } @@ -137,21 +131,8 @@ private String callApi(String url) throws IOException { * @throws IllegalStateException if HTTP code is different than 2xx */ private ResponseBody callUrl(String url, boolean authentication, @Nullable String acceptHeader) { - var requestBuilder = new Request.Builder() - .get() - .url(url) - .addHeader("User-Agent", httpConfig.getUserAgent()); - if (authentication) { - if (httpConfig.getToken() != null) { - requestBuilder.header("Authorization", "Bearer " + httpConfig.getToken()); - } else if (httpConfig.getLogin() != null) { - requestBuilder.header("Authorization", Credentials.basic(httpConfig.getLogin(), httpConfig.getPassword() != null ? httpConfig.getPassword() : "")); - } - } - if (acceptHeader != null) { - requestBuilder.header("Accept", acceptHeader); - } - Request request = requestBuilder.build(); + var httpClient = buildHttpClient(authentication); + var request = prepareRequest(url, acceptHeader); Response response; try { response = httpClient.newCall(request).execute(); @@ -164,4 +145,37 @@ private ResponseBody callUrl(String url, boolean authentication, @Nullable Strin } return response.body(); } + + private Request prepareRequest(String url, @org.jetbrains.annotations.Nullable String acceptHeader) { + var requestBuilder = new Request.Builder() + .get() + .url(url) + .addHeader("User-Agent", httpConfig.getUserAgent()); + if (acceptHeader != null) { + requestBuilder.header("Accept", acceptHeader); + } + return requestBuilder.build(); + } + + private OkHttpClient buildHttpClient(boolean authentication) { + if (authentication) { + return sharedHttpClient.newBuilder() + .addNetworkInterceptor(chain -> { + Request request = chain.request(); + if (httpConfig.getToken() != null) { + request = request.newBuilder() + .header("Authorization", "Bearer " + httpConfig.getToken()) + .build(); + } else if (httpConfig.getLogin() != null) { + request = request.newBuilder() + .header("Authorization", Credentials.basic(httpConfig.getLogin(), httpConfig.getPassword() != null ? httpConfig.getPassword() : "")) + .build(); + } + return chain.proceed(request); + }) + .build(); + } else { + return sharedHttpClient; + } + } } diff --git a/lib/src/test/java/org/sonarsource/scanner/lib/internal/http/ScannerHttpClientTest.java b/lib/src/test/java/org/sonarsource/scanner/lib/internal/http/ScannerHttpClientTest.java index 2c792b80..9fc3e3ec 100644 --- a/lib/src/test/java/org/sonarsource/scanner/lib/internal/http/ScannerHttpClientTest.java +++ b/lib/src/test/java/org/sonarsource/scanner/lib/internal/http/ScannerHttpClientTest.java @@ -29,6 +29,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.sonarsource.scanner.lib.ScannerProperties; import org.sonarsource.scanner.lib.internal.InternalProperties; @@ -51,6 +53,11 @@ class ScannerHttpClientTest { .options(wireMockConfig().dynamicPort()) .build(); + @RegisterExtension + static WireMockExtension redirectProxy = WireMockExtension.newInstance() + .options(wireMockConfig().dynamicPort()) + .build(); + @TempDir private Path sonarUserHome; @@ -168,6 +175,28 @@ void downloadFromExternalUrl_shouldNotPassAuth(@TempDir Path tmpFolder) throws E .withoutHeader("Authorization")); } + @ParameterizedTest + @ValueSource(ints = {301, 302, 303, 307, 308}) + void should_follow_redirects_and_preserve_authentication(int code) throws Exception { + Map props = new HashMap<>(); + props.put("sonar.login", "some_username"); + props.put("sonar.password", "some_password"); + ScannerHttpClient connection = create(redirectProxy.baseUrl(), props); + + redirectProxy.stubFor(get("/batch/index.txt") + .willReturn(aResponse() + .withHeader("Location", sonarqube.baseUrl() + "/batch/index.txt") + .withStatus(code))); + + answer(HELLO_WORLD); + String content = connection.callWebApi("/batch/index.txt"); + assertThat(content).isEqualTo(HELLO_WORLD); + + sonarqube.verify(getRequestedFor(anyUrl()) + .withHeader("Authorization", + equalTo("Basic " + Base64.getEncoder().encodeToString("some_username:some_password".getBytes(StandardCharsets.UTF_8))))); + } + private ScannerHttpClient create() { return create(sonarqube.baseUrl()); }