Browse Source

SONAR-8351 share OkHttpClient utilities

sonar-ws correctly configures OkHttpClient for the
support of HTTPS, proxy (incl. authentication), timeouts and user agent.
This code should be reused by web server and compute engine, for
example when sending webhook payloads.

The new class OkHttpClientProvider allows web server/CE
to instantiate and configure a single instance of OkHttpClient.
tags/6.2-RC1
Simon Brandhof 7 years ago
parent
commit
f050424587

+ 3
- 0
server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java View File

@@ -137,6 +137,7 @@ import org.sonar.server.user.DefaultUserFinder;
import org.sonar.server.user.DeprecatedUserFinder;
import org.sonar.server.user.index.UserIndex;
import org.sonar.server.user.index.UserIndexer;
import org.sonar.server.util.OkHttpClientProvider;
import org.sonar.server.view.index.ViewIndex;
import org.sonar.server.view.index.ViewIndexer;
import org.sonarqube.ws.Rules;
@@ -241,6 +242,8 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {

// issues
IssueIndex.class,

new OkHttpClientProvider()
};
}


+ 1
- 1
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java View File

@@ -105,7 +105,7 @@ public class ComputeEngineContainerImplTest {
);
assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize(
COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION
+ 24 // level 1
+ 25 // level 1
+ 46 // content of DaoModule
+ 2 // content of EsSearchModule
+ 62 // content of CorePropertyDefinitions

+ 2
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java View File

@@ -55,6 +55,7 @@ import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.search.EsSearchModule;
import org.sonar.server.setting.ThreadLocalSettings;
import org.sonar.server.user.ThreadLocalUserSession;
import org.sonar.server.util.OkHttpClientProvider;

public class PlatformLevel1 extends PlatformLevel {
private final Platform platform;
@@ -118,6 +119,7 @@ public class PlatformLevel1 extends PlatformLevel {
// issues
IssueIndex.class,

new OkHttpClientProvider(),
// Classes kept for backward compatibility of plugins/libs (like sonar-license) that are directly calling classes from the core
org.sonar.core.properties.PropertiesDao.class);
addAll(CorePropertyDefinitions.all());

+ 69
- 0
server/sonar-server/src/main/java/org/sonar/server/util/OkHttpClientProvider.java View File

@@ -0,0 +1,69 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.util;

import okhttp3.OkHttpClient;
import org.picocontainer.injectors.ProviderAdapter;
import org.sonar.api.SonarRuntime;
import org.sonar.api.ce.ComputeEngineSide;
import org.sonar.api.config.Settings;
import org.sonar.api.server.ServerSide;
import org.sonar.process.ProcessProperties;
import org.sonarqube.ws.client.OkHttpClientBuilder;

import static java.lang.String.format;

/**
* Provide a unique instance of {@link OkHttpClient} which configuration:
* <ul>
* <li>supports HTTPS</li>
* <li>supports proxy, including authentication, as defined by the properties like "http.proxyHost" in
* conf/sonar.properties</li>
* <li>has connect and read timeouts of 10 seconds each</li>
* <li>sends automatically the HTTP header "User-Agent" with value "SonarQube/{version}", for instance "SonarQube/6.2"</li>
* </ul>
*/
@ServerSide
@ComputeEngineSide
public class OkHttpClientProvider extends ProviderAdapter {

private static final int DEFAULT_CONNECT_TIMEOUT_IN_MS = 10_000;
private static final int DEFAULT_READ_TIMEOUT_IN_MS = 10_000;

private okhttp3.OkHttpClient okHttpClient;

/**
* @return a {@link OkHttpClient} singleton
*/
public OkHttpClient provide(Settings settings, SonarRuntime runtime) {
if (okHttpClient == null) {
OkHttpClientBuilder builder = new OkHttpClientBuilder();
builder.setConnectTimeoutMs(DEFAULT_CONNECT_TIMEOUT_IN_MS);
builder.setReadTimeoutMs(DEFAULT_READ_TIMEOUT_IN_MS);
// no need to define proxy URL as system-wide proxy is used and properly
// configured by bootstrap process.
builder.setProxyLogin(settings.getString(ProcessProperties.HTTP_PROXY_USER));
builder.setProxyPassword(settings.getString(ProcessProperties.HTTP_PROXY_PASSWORD));
builder.setUserAgent(format("SonarQube/%s", runtime.getApiVersion().toString()));
okHttpClient = builder.build();
}
return okHttpClient;
}
}

+ 86
- 0
server/sonar-server/src/test/java/org/sonar/server/util/OkHttpClientProviderTest.java View File

@@ -0,0 +1,86 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.util;

import java.io.IOException;
import java.util.Base64;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.SonarQubeSide;
import org.sonar.api.SonarRuntime;
import org.sonar.api.config.MapSettings;
import org.sonar.api.config.Settings;
import org.sonar.api.internal.SonarRuntimeImpl;
import org.sonar.api.utils.Version;

import static org.assertj.core.api.Assertions.assertThat;

public class OkHttpClientProviderTest {

private Settings settings = new MapSettings();
private SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.parse("6.2"), SonarQubeSide.SERVER);
private final OkHttpClientProvider underTest = new OkHttpClientProvider();

@Rule
public MockWebServer server = new MockWebServer();

@Test
public void get_returns_a_OkHttpClient_with_default_configuration() throws Exception {
OkHttpClient client = underTest.provide(settings, runtime);

assertThat(client.connectTimeoutMillis()).isEqualTo(10_000);
assertThat(client.readTimeoutMillis()).isEqualTo(10_000);
assertThat(client.proxy()).isNull();

RecordedRequest recordedRequest = call(client);
assertThat(recordedRequest.getHeader("User-Agent")).isEqualTo("SonarQube/6.2");
assertThat(recordedRequest.getHeader("Proxy-Authorization")).isNull();
}

@Test
public void get_returns_a_OkHttpClient_with_proxy_authentication() throws Exception {
settings.setProperty("http.proxyUser", "the-login");
settings.setProperty("http.proxyPassword", "the-password");

OkHttpClient client = underTest.provide(settings, runtime);
RecordedRequest recordedRequest = call(client);

assertThat(recordedRequest.getHeader("Proxy-Authorization")).isEqualTo("Basic " + Base64.getEncoder().encodeToString("the-login:the-password".getBytes()));
}

@Test
public void get_returns_a_singleton() {
OkHttpClient client1 = underTest.provide(settings, runtime);
OkHttpClient client2 = underTest.provide(settings, runtime);
assertThat(client2).isNotNull().isSameAs(client1);
}

private RecordedRequest call(OkHttpClient client) throws IOException, InterruptedException {
server.enqueue(new MockResponse().setBody("pong"));
client.newCall(new Request.Builder().url(server.url("/ping")).build()).execute();

return server.takeRequest();
}
}

+ 4
- 9
sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/BatchWsClientProviderTest.java View File

@@ -23,21 +23,18 @@ import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.sonar.batch.bootstrapper.EnvironmentInformation;
import org.sonar.scanner.bootstrap.BatchWsClient;
import org.sonar.scanner.bootstrap.BatchWsClientProvider;
import org.sonar.scanner.bootstrap.GlobalProperties;
import org.sonarqube.ws.client.HttpConnector;

import static org.assertj.core.api.Assertions.assertThat;

public class BatchWsClientProviderTest {

BatchWsClientProvider underTest = new BatchWsClientProvider();
EnvironmentInformation env = new EnvironmentInformation("Maven Plugin", "2.3");
private BatchWsClientProvider underTest = new BatchWsClientProvider();
private EnvironmentInformation env = new EnvironmentInformation("Maven Plugin", "2.3");

@Test
public void provide_client_with_default_settings() {
GlobalProperties settings = new GlobalProperties(new HashMap<String, String>());
GlobalProperties settings = new GlobalProperties(new HashMap<>());

BatchWsClient client = underTest.provide(settings, env);

@@ -48,7 +45,6 @@ public class BatchWsClientProviderTest {
assertThat(httpConnector.okHttpClient().proxy()).isNull();
assertThat(httpConnector.okHttpClient().connectTimeoutMillis()).isEqualTo(5_000);
assertThat(httpConnector.okHttpClient().readTimeoutMillis()).isEqualTo(60_000);
assertThat(httpConnector.userAgent()).isEqualTo("Maven Plugin/2.3");
}

@Test
@@ -66,12 +62,11 @@ public class BatchWsClientProviderTest {
HttpConnector httpConnector = (HttpConnector) client.wsConnector();
assertThat(httpConnector.baseUrl()).isEqualTo("https://here/sonarqube/");
assertThat(httpConnector.okHttpClient().proxy()).isNull();
assertThat(httpConnector.userAgent()).isEqualTo("Maven Plugin/2.3");
}

@Test
public void build_singleton() {
GlobalProperties settings = new GlobalProperties(new HashMap<String, String>());
GlobalProperties settings = new GlobalProperties(new HashMap<>());
BatchWsClient first = underTest.provide(settings, env);
BatchWsClient second = underTest.provide(settings, env);
assertThat(first).isSameAs(second);

+ 9
- 154
sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java View File

@@ -19,25 +19,11 @@
*/
package org.sonarqube.ws.client;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.Proxy;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.Call;
import okhttp3.ConnectionSpec;
import okhttp3.Credentials;
import okhttp3.Headers;
import okhttp3.HttpUrl;
@@ -52,7 +38,6 @@ 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.util.Arrays.asList;

/**
* Connect to any SonarQube server available through HTTP or HTTPS.
@@ -63,23 +48,21 @@ public class HttpConnector implements WsConnector {

public static final int DEFAULT_CONNECT_TIMEOUT_MILLISECONDS = 30_000;
public static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = 60_000;
private static final String NONE = "NONE";
private static final String P11KEYSTORE = "PKCS11";

/**
* Base URL with trailing slash, for instance "https://localhost/sonarqube/".
* It is required for further usage of {@link HttpUrl#resolve(String)}.
*/
private final HttpUrl baseUrl;
private final String userAgent;
private final String credentials;
private final String proxyCredentials;
private final OkHttpClient okHttpClient;

private HttpConnector(Builder builder) {
this.baseUrl = HttpUrl.parse(builder.url.endsWith("/") ? builder.url : format("%s/", builder.url));
checkArgument(this.baseUrl != null, "Malformed URL: '%s'", builder.url);
this.userAgent = builder.userAgent;

OkHttpClientBuilder okHttpClientBuilder = new OkHttpClientBuilder();
okHttpClientBuilder.setUserAgent(builder.userAgent);

if (isNullOrEmpty(builder.login)) {
// no login nor access token
@@ -89,129 +72,12 @@ public class HttpConnector implements WsConnector {
// the Basic credentials consider an empty password.
this.credentials = Credentials.basic(builder.login, nullToEmpty(builder.password));
}
// proxy credentials can be used on system-wide proxies, so even if builder.proxy is null
if (isNullOrEmpty(builder.proxyLogin)) {
this.proxyCredentials = null;
} else {
this.proxyCredentials = Credentials.basic(builder.proxyLogin, nullToEmpty(builder.proxyPassword));
}
this.okHttpClient = buildClient(builder);
}

private static OkHttpClient buildClient(Builder builder) {
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
if (builder.proxy != null) {
okHttpClientBuilder.proxy(builder.proxy);
}

okHttpClientBuilder.connectTimeout(builder.connectTimeoutMs, TimeUnit.MILLISECONDS);
okHttpClientBuilder.readTimeout(builder.readTimeoutMs, TimeUnit.MILLISECONDS);

ConnectionSpec tls = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.allEnabledTlsVersions()
.allEnabledCipherSuites()
.supportsTlsExtensions(true)
.build();
okHttpClientBuilder.connectionSpecs(asList(tls, ConnectionSpec.CLEARTEXT));
X509TrustManager systemDefaultTrustManager = systemDefaultTrustManager();
okHttpClientBuilder.sslSocketFactory(systemDefaultSslSocketFactory(systemDefaultTrustManager), systemDefaultTrustManager);

return okHttpClientBuilder.build();
}

private static X509TrustManager systemDefaultTrustManager() {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
return (X509TrustManager) trustManagers[0];
} catch (GeneralSecurityException e) {
// The system has no TLS. Just give up.
throw new AssertionError(e);
}
}

private static SSLSocketFactory systemDefaultSslSocketFactory(X509TrustManager trustManager) {
KeyManager[] defaultKeyManager;
try {
defaultKeyManager = getDefaultKeyManager();
} catch (Exception e) {
throw new IllegalStateException("Unable to get default key manager", e);
}
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(defaultKeyManager, new TrustManager[] {trustManager}, null);
return sslContext.getSocketFactory();
} catch (GeneralSecurityException e) {
// The system has no TLS. Just give up.
throw new AssertionError(e);
}
}

private static void logDebug(String msg) {
boolean debugEnabled = "all".equals(System.getProperty("javax.net.debug"));
if (debugEnabled) {
System.out.println(msg);
}
}

/**
* Inspired from sun.security.ssl.SSLContextImpl#getDefaultKeyManager()
*/
private static synchronized KeyManager[] getDefaultKeyManager() throws Exception {

final String defaultKeyStore = System.getProperty("javax.net.ssl.keyStore", "");
String defaultKeyStoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType());
String defaultKeyStoreProvider = System.getProperty("javax.net.ssl.keyStoreProvider", "");

logDebug("keyStore is : " + defaultKeyStore);
logDebug("keyStore type is : " + defaultKeyStoreType);
logDebug("keyStore provider is : " + defaultKeyStoreProvider);

if (P11KEYSTORE.equals(defaultKeyStoreType) && !NONE.equals(defaultKeyStore)) {
throw new IllegalArgumentException("if keyStoreType is " + P11KEYSTORE + ", then keyStore must be " + NONE);
}

KeyStore ks = null;
String defaultKeyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword", "");
char[] passwd = defaultKeyStorePassword.isEmpty() ? null : defaultKeyStorePassword.toCharArray();

/**
* Try to initialize key store.
*/
if (!defaultKeyStoreType.isEmpty()) {
logDebug("init keystore");
if (defaultKeyStoreProvider.isEmpty()) {
ks = KeyStore.getInstance(defaultKeyStoreType);
} else {
ks = KeyStore.getInstance(defaultKeyStoreType, defaultKeyStoreProvider);
}
if (!defaultKeyStore.isEmpty() && !NONE.equals(defaultKeyStore)) {
try (FileInputStream fs = new FileInputStream(defaultKeyStore)) {
ks.load(fs, passwd);
}
} else {
ks.load(null, passwd);
}
}

/*
* Try to initialize key manager.
*/
logDebug("init keymanager of type " + KeyManagerFactory.getDefaultAlgorithm());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());

if (P11KEYSTORE.equals(defaultKeyStoreType)) {
// do not pass key passwd if using token
kmf.init(ks, null);
} else {
kmf.init(ks, passwd);
}

return kmf.getKeyManagers();
okHttpClientBuilder.setProxy(builder.proxy);
okHttpClientBuilder.setProxyLogin(builder.proxyLogin);
okHttpClientBuilder.setProxyPassword(builder.proxyPassword);
okHttpClientBuilder.setConnectTimeoutMs(builder.connectTimeoutMs);
okHttpClientBuilder.setReadTimeoutMs(builder.readTimeoutMs);
this.okHttpClient = okHttpClientBuilder.build();
}

@Override
@@ -219,11 +85,6 @@ public class HttpConnector implements WsConnector {
return baseUrl.url().toExternalForm();
}

@CheckForNull
public String userAgent() {
return userAgent;
}

public OkHttpClient okHttpClient() {
return okHttpClient;
}
@@ -287,12 +148,6 @@ public class HttpConnector implements WsConnector {
if (credentials != null) {
okHttpRequestBuilder.header("Authorization", credentials);
}
if (proxyCredentials != null) {
okHttpRequestBuilder.header("Proxy-Authorization", proxyCredentials);
}
if (userAgent != null) {
okHttpRequestBuilder.addHeader("User-Agent", userAgent);
}
return okHttpRequestBuilder;
}


+ 248
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/OkHttpClientBuilder.java View File

@@ -0,0 +1,248 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonarqube.ws.client;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.Proxy;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.ConnectionSpec;
import okhttp3.Credentials;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import static com.google.common.base.Strings.nullToEmpty;
import static java.util.Arrays.asList;

/**
* Helper to build an instance of {@link okhttp3.OkHttpClient} that
* correctly supports HTTPS and proxy authentication. It also handles
* sending of User-Agent header.
*/
public class OkHttpClientBuilder {

private static final String NONE = "NONE";
private static final String P11KEYSTORE = "PKCS11";

private String userAgent;
private Proxy proxy;
private String proxyLogin;
private String proxyPassword;
private long connectTimeoutMs = -1;
private long readTimeoutMs = -1;

/**
* Optional User-Agent. If set, then all the requests sent by the
* {@link OkHttpClient} will include the header "User-Agent".
*/
public OkHttpClientBuilder setUserAgent(@Nullable String s) {
this.userAgent = s;
return this;
}

/**
* Optional proxy. If set, then all the requests sent by the
* {@link OkHttpClient} will reach the proxy. If not set,
* then the system-wide proxy is used.
*/
public OkHttpClientBuilder setProxy(@Nullable Proxy proxy) {
this.proxy = proxy;
return this;
}

/**
* Login required for proxy authentication.
*/
public OkHttpClientBuilder setProxyLogin(@Nullable String s) {
this.proxyLogin = s;
return this;
}

/**
* Password used for proxy authentication. It is ignored if
* proxy login is not defined (see {@link #setProxyLogin(String)}).
* It can be null or empty when login is defined.
*/
public OkHttpClientBuilder setProxyPassword(@Nullable String s) {
this.proxyPassword = s;
return this;
}

/**
* Sets the default connect timeout for new connections. A value of 0 means no timeout.
* Default is defined by OkHttp (10 seconds in OkHttp 3.3).
*/
public OkHttpClientBuilder setConnectTimeoutMs(long l) {
if (l < 0) {
throw new IllegalArgumentException("Connect timeout must be positive. Got " + l);
}
this.connectTimeoutMs = l;
return this;
}

/**
* 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).
*/
public OkHttpClientBuilder setReadTimeoutMs(long l) {
if (l < 0) {
throw new IllegalArgumentException("Read timeout must be positive. Got " + l);
}
this.readTimeoutMs = l;
return this;
}

public OkHttpClient build() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.proxy(proxy);
if (connectTimeoutMs >= 0) {
builder.connectTimeout(connectTimeoutMs, TimeUnit.MILLISECONDS);
}
if (readTimeoutMs >= 0) {
builder.readTimeout(readTimeoutMs, TimeUnit.MILLISECONDS);
}
builder.addInterceptor(this::completeHeaders);

ConnectionSpec tls = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.allEnabledTlsVersions()
.allEnabledCipherSuites()
.supportsTlsExtensions(true)
.build();
builder.connectionSpecs(asList(tls, ConnectionSpec.CLEARTEXT));
X509TrustManager systemDefaultTrustManager = systemDefaultTrustManager();
builder.sslSocketFactory(systemDefaultSslSocketFactory(systemDefaultTrustManager), systemDefaultTrustManager);

return builder.build();
}

private Response completeHeaders(Interceptor.Chain chain) throws IOException {
Request.Builder newRequest = chain.request().newBuilder();
if (userAgent != null) {
newRequest.header("User-Agent", userAgent);
}
if (proxyLogin != null) {
newRequest.header("Proxy-Authorization", Credentials.basic(proxyLogin, nullToEmpty(proxyPassword)));
}
return chain.proceed(newRequest.build());
}

private static X509TrustManager systemDefaultTrustManager() {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
return (X509TrustManager) trustManagers[0];
} catch (GeneralSecurityException e) {
// The system has no TLS. Just give up.
throw new AssertionError(e);
}
}

private static SSLSocketFactory systemDefaultSslSocketFactory(X509TrustManager trustManager) {
KeyManager[] defaultKeyManager;
try {
defaultKeyManager = getDefaultKeyManager();
} catch (Exception e) {
throw new IllegalStateException("Unable to get default key manager", e);
}
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(defaultKeyManager, new TrustManager[] {trustManager}, null);
return sslContext.getSocketFactory();
} catch (GeneralSecurityException e) {
// The system has no TLS. Just give up.
throw new AssertionError(e);
}
}

private static void logDebug(String msg) {
boolean debugEnabled = "all".equals(System.getProperty("javax.net.debug"));
if (debugEnabled) {
System.out.println(msg);
}
}

/**
* Inspired from sun.security.ssl.SSLContextImpl#getDefaultKeyManager()
*/
private static synchronized KeyManager[] getDefaultKeyManager() throws Exception {
final String defaultKeyStore = System.getProperty("javax.net.ssl.keyStore", "");
String defaultKeyStoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType());
String defaultKeyStoreProvider = System.getProperty("javax.net.ssl.keyStoreProvider", "");

logDebug("keyStore is : " + defaultKeyStore);
logDebug("keyStore type is : " + defaultKeyStoreType);
logDebug("keyStore provider is : " + defaultKeyStoreProvider);

if (P11KEYSTORE.equals(defaultKeyStoreType) && !NONE.equals(defaultKeyStore)) {
throw new IllegalArgumentException("if keyStoreType is " + P11KEYSTORE + ", then keyStore must be " + NONE);
}

KeyStore ks = null;
String defaultKeyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword", "");
char[] passwd = defaultKeyStorePassword.isEmpty() ? null : defaultKeyStorePassword.toCharArray();

// Try to initialize key store.
if (!defaultKeyStoreType.isEmpty()) {
logDebug("init keystore");
if (defaultKeyStoreProvider.isEmpty()) {
ks = KeyStore.getInstance(defaultKeyStoreType);
} else {
ks = KeyStore.getInstance(defaultKeyStoreType, defaultKeyStoreProvider);
}
if (!defaultKeyStore.isEmpty() && !NONE.equals(defaultKeyStore)) {
try (FileInputStream fs = new FileInputStream(defaultKeyStore)) {
ks.load(fs, passwd);
}
} else {
ks.load(null, passwd);
}
}

// Try to initialize key manager.
logDebug("init keymanager of type " + KeyManagerFactory.getDefaultAlgorithm());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());

if (P11KEYSTORE.equals(defaultKeyStoreType)) {
// do not pass key passwd if using token
kmf.init(ks, null);
} else {
kmf.init(ks, passwd);
}

return kmf.getKeyManagers();
}
}

+ 3
- 4
sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java View File

@@ -49,10 +49,9 @@ public class HttpConnectorTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();

MockWebServer server;
String serverUrl;

HttpConnector underTest;
private MockWebServer server;
private String serverUrl;
private HttpConnector underTest;

@Before
public void setUp() throws Exception {

+ 60
- 0
sonar-ws/src/test/java/org/sonarqube/ws/client/OkHttpClientBuilderTest.java View File

@@ -0,0 +1,60 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonarqube.ws.client;

import okhttp3.OkHttpClient;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static org.assertj.core.api.Assertions.assertThat;

public class OkHttpClientBuilderTest {

@Rule
public ExpectedException expectedException = ExpectedException.none();

private OkHttpClientBuilder underTest = new OkHttpClientBuilder();

@Test
public void build_default_instance_of_OkHttpClient() {
OkHttpClient okHttpClient = underTest.build();

assertThat(okHttpClient.proxy()).isNull();
assertThat(okHttpClient.interceptors()).hasSize(1);
assertThat(okHttpClient.sslSocketFactory()).isNotNull();
}

@Test
public void build_throws_IAE_if_connect_timeout_is_negative() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Connect timeout must be positive. Got -10");

underTest.setConnectTimeoutMs(-10);
}

@Test
public void build_throws_IAE_if_read_timeout_is_negative() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Read timeout must be positive. Got -10");

underTest.setReadTimeoutMs(-10);
}
}

Loading…
Cancel
Save