diff options
author | Duarte Meneses <duarte.meneses@sonarsource.com> | 2017-05-23 15:08:17 +0200 |
---|---|---|
committer | Julien HENRY <julien.henry@sonarsource.com> | 2017-09-15 09:57:00 +0200 |
commit | 4f3018eb093f9c2d5406df7674eb0e9a7b50e44c (patch) | |
tree | 4187b0d2e72d98c8c1b3a26dd33da1615bdd0150 /sonar-ws/src | |
parent | 1de512a131f84fb1b6656150347d2b075b478780 (diff) | |
download | sonarqube-4f3018eb093f9c2d5406df7674eb0e9a7b50e44c.tar.gz sonarqube-4f3018eb093f9c2d5406df7674eb0e9a7b50e44c.zip |
SONAR-9301 Support HTTP redirect on scanner side
Diffstat (limited to 'sonar-ws/src')
3 files changed, 100 insertions, 21 deletions
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java index 6569eb3535b..9d4c7fdc1ea 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java @@ -40,6 +40,10 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.base.Strings.nullToEmpty; import static java.lang.String.format; +import static java.net.HttpURLConnection.HTTP_MOVED_PERM; +import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; +import static okhttp3.internal.http.StatusLine.HTTP_PERM_REDIRECT; +import static okhttp3.internal.http.StatusLine.HTTP_TEMP_REDIRECT; /** * Connect to any SonarQube server available through HTTP or HTTPS. @@ -56,9 +60,9 @@ public class HttpConnector implements WsConnector { * It is required for further usage of {@link HttpUrl#resolve(String)}. */ private final HttpUrl baseUrl; - private final String credentials; private final String systemPassCode; private final OkHttpClient okHttpClient; + private final OkHttpClient noRedirectOkHttpClient; private HttpConnector(Builder builder) { this.baseUrl = HttpUrl.parse(builder.url.endsWith("/") ? builder.url : format("%s/", builder.url)); @@ -67,13 +71,10 @@ public class HttpConnector implements WsConnector { OkHttpClientBuilder okHttpClientBuilder = new OkHttpClientBuilder(); okHttpClientBuilder.setUserAgent(builder.userAgent); - if (isNullOrEmpty(builder.login)) { - // no login nor access token - this.credentials = null; - } else { + if (!isNullOrEmpty(builder.login)) { // password is null when login represents an access token. In this case // the Basic credentials consider an empty password. - this.credentials = Credentials.basic(builder.login, nullToEmpty(builder.password)); + okHttpClientBuilder.setCredentials(Credentials.basic(builder.login, nullToEmpty(builder.password))); } this.systemPassCode = builder.systemPassCode; okHttpClientBuilder.setProxy(builder.proxy); @@ -84,6 +85,14 @@ public class HttpConnector implements WsConnector { okHttpClientBuilder.setSSLSocketFactory(builder.sslSocketFactory); okHttpClientBuilder.setTrustManager(builder.sslTrustManager); this.okHttpClient = okHttpClientBuilder.build(); + this.noRedirectOkHttpClient = newClientWithoutRedirect(this.okHttpClient); + } + + private static OkHttpClient newClientWithoutRedirect(OkHttpClient client) { + return client.newBuilder() + .followRedirects(false) + .followSslRedirects(false) + .build(); } @Override @@ -111,7 +120,7 @@ public class HttpConnector implements WsConnector { completeUrlQueryParameters(getRequest, urlBuilder); Request.Builder okRequestBuilder = prepareOkRequestBuilder(getRequest, urlBuilder).get(); - return doCall(okRequestBuilder.build()); + return new OkHttpResponse(doCall(okHttpClient, okRequestBuilder.build())); } private WsResponse post(PostRequest postRequest) { @@ -141,8 +150,10 @@ public class HttpConnector implements WsConnector { }); body = bodyBuilder.build(); } - Request.Builder reqBuilder = prepareOkRequestBuilder(postRequest, urlBuilder); - return doCall(reqBuilder.post(body).build()); + Request.Builder okRequestBuilder = prepareOkRequestBuilder(postRequest, urlBuilder).post(body); + Response response = doCall(noRedirectOkHttpClient, okRequestBuilder.build()); + response = checkRedirect(response); + return new OkHttpResponse(response); } private HttpUrl.Builder prepareUrlBuilder(WsRequest wsRequest) { @@ -152,7 +163,7 @@ public class HttpConnector implements WsConnector { .newBuilder(); } - private static void completeUrlQueryParameters(BaseRequest request, HttpUrl.Builder urlBuilder) { + private static void completeUrlQueryParameters(BaseRequest<?> request, HttpUrl.Builder urlBuilder) { request.getParameters().getKeys() .forEach(key -> request.getParameters().getValues(key) .forEach(value -> urlBuilder.addQueryParameter(key, value))); @@ -163,27 +174,57 @@ public class HttpConnector implements WsConnector { .url(urlBuilder.build()) .header("Accept", getRequest.getMediaType()) .header("Accept-Charset", "UTF-8"); - if (credentials != null) { - okHttpRequestBuilder.header("Authorization", credentials); - } if (systemPassCode != null) { okHttpRequestBuilder.header("X-Sonar-Passcode", systemPassCode); } - getRequest.getHeaders().getNames().forEach(name -> - okHttpRequestBuilder.header(name, getRequest.getHeaders().getValue(name).get())); + getRequest.getHeaders().getNames().forEach(name -> okHttpRequestBuilder.header(name, getRequest.getHeaders().getValue(name).get())); return okHttpRequestBuilder; } - private OkHttpResponse doCall(Request okRequest) { - Call call = okHttpClient.newCall(okRequest); + private static Response doCall(OkHttpClient client, Request okRequest) { + Call call = client.newCall(okRequest); try { - Response okResponse = call.execute(); - return new OkHttpResponse(okResponse); + return call.execute(); } catch (IOException e) { throw new IllegalStateException("Fail to request " + okRequest.url(), e); } } + private Response checkRedirect(Response response) { + switch (response.code()) { + case HTTP_MOVED_PERM: + case HTTP_MOVED_TEMP: + case HTTP_TEMP_REDIRECT: + case HTTP_PERM_REDIRECT: + // OkHttpClient does not follow the redirect with the same HTTP method. A POST is + // redirected to a GET. Because of that the redirect must be manually implemented. + // See: + // https://github.com/square/okhttp/blob/07309c1c7d9e296014268ebd155ebf7ef8679f6c/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java#L316 + // https://github.com/square/okhttp/issues/936#issuecomment-266430151 + return followPostRedirect(response); + default: + return response; + } + } + + private Response followPostRedirect(Response response) { + String location = response.header("Location"); + if (location == null) { + throw new IllegalStateException(format("Missing HTTP header 'Location' in redirect of %s", response.request().url())); + } + HttpUrl url = response.request().url().resolve(location); + + // Don't follow redirects to unsupported protocols. + if (url == null) { + throw new IllegalStateException(format("Unsupported protocol in redirect of %s to %s", response.request().url(), location)); + } + + Request.Builder redirectRequest = response.request().newBuilder(); + redirectRequest.post(response.request().body()); + response.body().close(); + return doCall(noRedirectOkHttpClient, redirectRequest.url(url).build()); + } + /** * @since 5.5 */ diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/OkHttpClientBuilder.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/OkHttpClientBuilder.java index a9d4985fff5..8737a68ea0c 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/OkHttpClientBuilder.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/OkHttpClientBuilder.java @@ -63,6 +63,7 @@ public class OkHttpClientBuilder { private String userAgent; private Proxy proxy; + private String credentials; private String proxyLogin; private String proxyPassword; private long connectTimeoutMs = -1; @@ -138,6 +139,13 @@ public class OkHttpClientBuilder { } /** + * Set credentials that will be passed on every request + */ + public void setCredentials(String credentials) { + this.credentials = credentials; + } + + /** * Sets the default read timeout for new connections. A value of 0 means no timeout. * Default is defined by OkHttp (10 seconds in OkHttp 3.3). */ @@ -158,7 +166,7 @@ public class OkHttpClientBuilder { if (readTimeoutMs >= 0) { builder.readTimeout(readTimeoutMs, TimeUnit.MILLISECONDS); } - builder.addNetworkInterceptor(this::addUserAgent); + builder.addNetworkInterceptor(this::addHeaders); if (proxyLogin != null) { builder.proxyAuthenticator((route, response) -> { if (response.request().header(PROXY_AUTHORIZATION) != null) { @@ -187,11 +195,14 @@ public class OkHttpClientBuilder { return builder.build(); } - private Response addUserAgent(Interceptor.Chain chain) throws IOException { + private Response addHeaders(Interceptor.Chain chain) throws IOException { Request.Builder newRequest = chain.request().newBuilder(); if (userAgent != null) { newRequest.header("User-Agent", userAgent); } + if (credentials != null) { + newRequest.header("Authorization", credentials); + } return chain.proceed(newRequest.build()); } @@ -285,4 +296,5 @@ public class OkHttpClientBuilder { return kmf.getKeyManagers(); } + } diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java index 5c07cd7021b..4e1ca719ac4 100644 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java @@ -20,6 +20,7 @@ package org.sonarqube.ws.client; import java.io.File; +import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; import java.util.List; @@ -71,6 +72,31 @@ public class HttpConnectorTest { } @Test + public void follow_redirects_post() throws IOException, InterruptedException { + MockWebServer server2 = new MockWebServer(); + server2.start(); + server2.url("").url().toString(); + + server.enqueue(new MockResponse() + .setResponseCode(302) + .setHeader("Location", server2.url("").url().toString())); + + server2.enqueue(new MockResponse() + .setResponseCode(200)); + + underTest = HttpConnector.newBuilder().url(serverUrl).build(); + PostRequest request = new PostRequest("api/ce/submit").setParam("projectKey", "project"); + WsResponse response = underTest.call(request); + + RecordedRequest recordedRequest = server2.takeRequest(); + + assertThat(recordedRequest.getMethod()).isEqualTo("POST"); + assertThat(recordedRequest.getBody().readUtf8()).isEqualTo("projectKey=project"); + assertThat(response.requestUrl()).isEqualTo(server2.url("").url().toString()); + assertThat(response.code()).isEqualTo(200); + } + + @Test public void test_default_settings() throws Exception { answerHelloWorld(); underTest = HttpConnector.newBuilder().url(serverUrl).build(); |