- true
+ false
central
false
published
diff --git a/client/src/main/java/io/split/client/HttpClientDynamicCredentials.java b/client/src/main/java/io/split/client/HttpClientDynamicCredentials.java
new file mode 100644
index 00000000..ebcbe667
--- /dev/null
+++ b/client/src/main/java/io/split/client/HttpClientDynamicCredentials.java
@@ -0,0 +1,26 @@
+package io.split.client;
+
+import io.split.client.dtos.BearerCredentialsProvider;
+import org.apache.hc.client5.http.auth.AuthScope;
+import org.apache.hc.client5.http.auth.BearerToken;
+import org.apache.hc.client5.http.auth.Credentials;
+import org.apache.hc.core5.http.protocol.HttpContext;
+
+class HttpClientDynamicCredentials implements org.apache.hc.client5.http.auth.CredentialsProvider {
+
+ private final BearerCredentialsProvider _bearerCredentialsProvider;
+
+ public HttpClientDynamicCredentials (BearerCredentialsProvider bearerCredentialsProvider) {
+ _bearerCredentialsProvider = bearerCredentialsProvider;
+ }
+
+ @Override
+ public Credentials getCredentials(AuthScope authScope, HttpContext context) {
+
+ // This Provider is invoked every time a request is made.
+ // This should invoke a user-custom provider responsible for:
+ return new BearerToken(_bearerCredentialsProvider.getToken());
+ }
+
+}
+
diff --git a/client/src/main/java/io/split/client/SplitClientConfig.java b/client/src/main/java/io/split/client/SplitClientConfig.java
index fd312c3b..f32b9b09 100644
--- a/client/src/main/java/io/split/client/SplitClientConfig.java
+++ b/client/src/main/java/io/split/client/SplitClientConfig.java
@@ -1,5 +1,6 @@
package io.split.client;
+import io.split.client.dtos.ProxyConfiguration;
import io.split.client.impressions.ImpressionListener;
import io.split.client.impressions.ImpressionsManager;
import io.split.client.utils.FileTypeEnum;
@@ -8,6 +9,7 @@
import io.split.storages.enums.OperationMode;
import io.split.storages.enums.StorageMode;
import org.apache.hc.core5.http.HttpHost;
+import org.slf4j.LoggerFactory;
import pluggable.CustomStorageWrapper;
import java.io.IOException;
@@ -27,6 +29,7 @@
*/
public class SplitClientConfig {
+ private static final org.slf4j.Logger _log = LoggerFactory.getLogger(SplitClientConfig.class);
public static final String LOCALHOST_DEFAULT_FILE = "split.yaml";
public static final String SDK_ENDPOINT = "https://sdk.split.io";
public static final String EVENTS_ENDPOINT = "https://events.split.io";
@@ -34,6 +37,14 @@ public class SplitClientConfig {
public static final String STREAMING_ENDPOINT = "https://streaming.split.io/sse";
public static final String TELEMETRY_ENDPOINT = "https://telemetry.split.io/api/v1";
+ public static class HttpScheme {
+ private HttpScheme() {
+ throw new IllegalStateException("Utility class");
+ }
+ public static final String HTTP = "http";
+ public static final String HTTPS = "https";
+ }
+
private final String _endpoint;
private final String _eventsEndpoint;
@@ -82,6 +93,7 @@ public class SplitClientConfig {
private final ThreadFactory _threadFactory;
// Proxy configs
+ private final ProxyConfiguration _proxyConfiguration;
private final HttpHost _proxy;
private final String _proxyUsername;
private final String _proxyPassword;
@@ -118,6 +130,7 @@ private SplitClientConfig(String endpoint,
HttpHost proxy,
String proxyUsername,
String proxyPassword,
+ ProxyConfiguration proxyConfiguration,
int eventsQueueSize,
long eventSendIntervalInMillis,
int maxStringLength,
@@ -171,6 +184,7 @@ private SplitClientConfig(String endpoint,
_proxy = proxy;
_proxyUsername = proxyUsername;
_proxyPassword = proxyPassword;
+ _proxyConfiguration = proxyConfiguration;
_eventsQueueSize = eventsQueueSize;
_eventSendIntervalInMillis = eventSendIntervalInMillis;
_maxStringLength = maxStringLength;
@@ -302,6 +316,10 @@ public String proxyPassword() {
return _proxyPassword;
}
+ public ProxyConfiguration proxyConfiguration() {
+ return _proxyConfiguration;
+ }
+
public long eventSendIntervalInMillis() {
return _eventSendIntervalInMillis;
}
@@ -417,8 +435,8 @@ public boolean isSdkEndpointOverridden() {
}
public CustomHttpModule alternativeHTTPModule() { return _alternativeHTTPModule; }
- public static final class Builder {
+ public static final class Builder {
private String _endpoint = SDK_ENDPOINT;
private boolean _endpointSet = false;
private String _eventsEndpoint = EVENTS_ENDPOINT;
@@ -442,6 +460,7 @@ public static final class Builder {
private int _proxyPort = -1;
private String _proxyUsername;
private String _proxyPassword;
+ private ProxyConfiguration _proxyConfiguration;
private int _eventsQueueSize = 500;
private long _eventSendIntervalInMillis = 30 * (long)1000;
private int _maxStringLength = 250;
@@ -734,10 +753,14 @@ public Builder waitBeforeShutdown(int waitTime) {
/**
* The host location of the proxy. Default is localhost.
+ * @deprecated
+ * This method is deprecated.
+ * Use {@link ProxyConfiguration)} instead.
*
* @param proxyHost location of the proxy
* @return this builder
*/
+ @Deprecated
public Builder proxyHost(String proxyHost) {
_proxyHost = proxyHost;
return this;
@@ -745,10 +768,14 @@ public Builder proxyHost(String proxyHost) {
/**
* The port of the proxy. Default is -1.
+ * @deprecated
+ * This method is deprecated.
+ *
Use {@link ProxyConfiguration)} instead.
*
* @param proxyPort port for the proxy
* @return this builder
*/
+ @Deprecated
public Builder proxyPort(int proxyPort) {
_proxyPort = proxyPort;
return this;
@@ -756,10 +783,14 @@ public Builder proxyPort(int proxyPort) {
/**
* Set the username for authentication against the proxy (if proxy settings are enabled). (Optional).
+ * @deprecated
+ * This method is deprecated.
+ *
Use {@link ProxyConfiguration)} instead.
*
* @param proxyUsername
* @return this builder
*/
+ @Deprecated
public Builder proxyUsername(String proxyUsername) {
_proxyUsername = proxyUsername;
return this;
@@ -776,6 +807,17 @@ public Builder proxyPassword(String proxyPassword) {
return this;
}
+ /**
+ * Set the mtls authentication against the proxy (if proxy settings are enabled). (Optional).
+ *
+ * @param proxyConfiguration
+ * @return this builder
+ */
+ public Builder proxyConfiguration(ProxyConfiguration proxyConfiguration) {
+ _proxyConfiguration = proxyConfiguration;
+ return this;
+ }
+
/**
* Disables running destroy() on shutdown by default.
*
@@ -927,7 +969,7 @@ public Builder operationMode(OperationMode mode) {
/**
*
- * @param storage mode
+ * @param mode
* @return this builder
*/
public Builder storageMode(StorageMode mode) {
@@ -1096,6 +1138,26 @@ private void verifyAlternativeClient() {
}
}
+ private void verifyProxy() {
+ if (_proxyConfiguration == null)
+ return;
+
+ if (_proxyPort != -1) {
+ _log.warn("Both the deprecated proxy configuration methods (`proxyHost`, `proxyPort`, `proxyUsername`, or `proxyPassword`) " +
+ "and the new `ProxyConfiguration` builder are being used. `ProxyConfiguration` will take precedence.");
+ }
+
+ if (!(_proxyConfiguration.getHost().getSchemeName().equals(HttpScheme.HTTP) ||
+ _proxyConfiguration.getHost().getSchemeName().equals(HttpScheme.HTTPS))) {
+ throw new IllegalArgumentException("Proxy scheme must be either http or https.");
+ }
+
+ if ((_proxyConfiguration.getP12File() != null && _proxyConfiguration.getPassKey() == null) ||
+ (_proxyConfiguration.getP12File() == null && _proxyConfiguration.getPassKey() != null)) {
+ throw new IllegalArgumentException("Proxy mTLS must have p12 file path and name, and pass phrase.");
+ }
+ }
+
public SplitClientConfig build() {
verifyRates();
@@ -1108,6 +1170,8 @@ public SplitClientConfig build() {
verifyAlternativeClient();
+ verifyProxy();
+
if (_numThreadsForSegmentFetch <= 0) {
throw new IllegalArgumentException("Number of threads for fetching segments MUST be greater than zero");
}
@@ -1133,6 +1197,7 @@ public SplitClientConfig build() {
proxy(),
_proxyUsername,
_proxyPassword,
+ _proxyConfiguration,
_eventsQueueSize,
_eventSendIntervalInMillis,
_maxStringLength,
diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java
index 9932cbf8..586e7fde 100644
--- a/client/src/main/java/io/split/client/SplitFactoryImpl.java
+++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java
@@ -1,6 +1,7 @@
package io.split.client;
import com.google.common.io.Files;
+import io.split.client.dtos.BearerCredentialsProvider;
import io.split.client.dtos.Metadata;
import io.split.client.events.EventsSender;
import io.split.client.events.EventsStorage;
@@ -105,6 +106,7 @@
import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
+import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.ssl.TLS;
import org.apache.hc.core5.ssl.SSLContexts;
@@ -113,11 +115,14 @@
import org.slf4j.LoggerFactory;
import pluggable.CustomStorageWrapper;
+import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
+import java.nio.file.Paths;
+import java.security.KeyStore;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import java.util.HashSet;
@@ -517,9 +522,12 @@ public boolean isDestroyed() {
protected static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClientConfig config,
SDKMetadata sdkMetadata, RequestDecorator requestDecorator)
- throws URISyntaxException {
+ throws URISyntaxException, IOException {
+
+ SSLContext sslContext = buildSSLContext(config);
+
SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
- .setSslContext(SSLContexts.createSystemDefault())
+ .setSslContext(sslContext)
.setTlsVersions(TLS.V_1_1, TLS.V_1_2)
.build();
@@ -545,7 +553,7 @@ protected static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClie
.addResponseInterceptorLast((new GzipDecoderResponseInterceptor()));
// Set up proxy is it exists
- if (config.proxy() != null) {
+ if (config.proxy() != null || config.proxyConfiguration() != null) {
httpClientbuilder = setupProxy(httpClientbuilder, config);
}
@@ -556,13 +564,15 @@ protected static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClie
}
private static CloseableHttpClient buildSSEdHttpClient(String apiToken, SplitClientConfig config,
- SDKMetadata sdkMetadata) {
+ SDKMetadata sdkMetadata) throws IOException {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(Timeout.ofMilliseconds(SSE_CONNECT_TIMEOUT))
.build();
+ SSLContext sslContext = buildSSLContext(config);
+
SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
- .setSslContext(SSLContexts.createSystemDefault())
+ .setSslContext(sslContext)
.setTlsVersions(TLS.V_1_1, TLS.V_1_2)
.build();
@@ -582,28 +592,92 @@ private static CloseableHttpClient buildSSEdHttpClient(String apiToken, SplitCli
.addRequestInterceptorLast(ClientKeyInterceptorFilter.instance(apiToken));
// Set up proxy is it exists
- if (config.proxy() != null) {
+ if (config.proxy() != null || config.proxyConfiguration() != null) {
httpClientbuilder = setupProxy(httpClientbuilder, config);
}
return httpClientbuilder.build();
}
+ private static SSLContext buildSSLContext(SplitClientConfig config) throws IOException, NullPointerException {
+ SSLContext sslContext;
+ if (config.proxyConfiguration() != null && config.proxyConfiguration().getP12File() != null) {
+ _log.debug("Proxy setup using mTLS");
+ InputStream keystoreStream = null;
+ try {
+ KeyStore keyStore = KeyStore.getInstance("PKCS12");
+ keystoreStream = java.nio.file.Files.newInputStream(Paths.get(config.proxyConfiguration().getP12File()));
+ keyStore.load(keystoreStream, config.proxyConfiguration().getPassKey().toCharArray());
+ sslContext = SSLContexts.custom()
+ .loadKeyMaterial(keyStore, config.proxyConfiguration().getPassKey().toCharArray())
+ .build();
+ } catch (Exception e) {
+ _log.error("Exception caught while processing p12 file for Proxy mTLS auth: ", e);
+ _log.warn("Ignoring p12 mTLS config and switching to default context");
+ sslContext = SSLContexts.createSystemDefault();
+ } finally {
+ if (keystoreStream != null) {
+ keystoreStream.close();
+ }
+ }
+ } else {
+ sslContext = SSLContexts.createSystemDefault();
+ }
+ return sslContext;
+ }
+
private static HttpClientBuilder setupProxy(HttpClientBuilder httpClientbuilder, SplitClientConfig config) {
_log.info("Initializing Split SDK with proxy settings");
- DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(config.proxy());
- httpClientbuilder.setRoutePlanner(routePlanner);
+ if (config.proxyConfiguration() != null) {
+ return useProxyConfiguration(httpClientbuilder, config);
+ } else {
+ return useLegacyProxyConfiguration(httpClientbuilder, config);
+ }
+ }
+ private static HttpClientBuilder useLegacyProxyConfiguration(HttpClientBuilder httpClientbuilder, SplitClientConfig config) {
+ HttpHost proxyHost = config.proxy();
+ httpClientbuilder = addProxyHost(httpClientbuilder, proxyHost);
if (config.proxyUsername() != null && config.proxyPassword() != null) {
- _log.debug("Proxy setup using credentials");
- BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
- AuthScope siteScope = new AuthScope(config.proxy().getHostName(), config.proxy().getPort());
- Credentials siteCreds = new UsernamePasswordCredentials(config.proxyUsername(),
- config.proxyPassword().toCharArray());
- credsProvider.setCredentials(siteScope, siteCreds);
- httpClientbuilder.setDefaultCredentialsProvider(credsProvider);
+ return addProxyBasicAuth(httpClientbuilder, proxyHost, config.proxyUsername(), config.proxyPassword());
+ }
+
+ return httpClientbuilder;
+ }
+
+ private static HttpClientBuilder useProxyConfiguration(HttpClientBuilder httpClientbuilder, SplitClientConfig config) {
+ HttpHost proxyHost = config.proxyConfiguration().getHost();
+ httpClientbuilder = addProxyHost(httpClientbuilder, proxyHost);
+ if (config.proxyConfiguration().getProxyCredentialsProvider() == null) {
+ return httpClientbuilder;
+ }
+
+ if (config.proxyConfiguration().getProxyCredentialsProvider() instanceof io.split.client.dtos.BasicCredentialsProvider) {
+ io.split.client.dtos.BasicCredentialsProvider basicAuth =
+ (io.split.client.dtos.BasicCredentialsProvider) config.proxyConfiguration().getProxyCredentialsProvider();
+ return addProxyBasicAuth(httpClientbuilder, proxyHost, basicAuth.getUsername(), basicAuth.getPassword());
}
+ _log.debug("Proxy setup using Bearer token");
+ httpClientbuilder.setDefaultCredentialsProvider(new HttpClientDynamicCredentials(
+ (BearerCredentialsProvider) config.proxyConfiguration().getProxyCredentialsProvider()));
+ return httpClientbuilder;
+ }
+
+ private static HttpClientBuilder addProxyHost(HttpClientBuilder httpClientbuilder, HttpHost proxyHost) {
+ DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxyHost);
+ httpClientbuilder.setRoutePlanner(routePlanner);
+ return httpClientbuilder;
+ }
+
+ private static HttpClientBuilder addProxyBasicAuth(HttpClientBuilder httpClientbuilder, HttpHost proxyHost, String userName, String password) {
+ _log.debug("Proxy setup using Basic authentication");
+ BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
+ AuthScope siteScope = new AuthScope(proxyHost.getHostName(), proxyHost.getPort());
+ Credentials siteCreds = new UsernamePasswordCredentials(userName,
+ password.toCharArray());
+ credsProvider.setCredentials(siteScope, siteCreds);
+ httpClientbuilder.setDefaultCredentialsProvider(credsProvider);
return httpClientbuilder;
}
diff --git a/client/src/main/java/io/split/client/dtos/BasicCredentialsProvider.java b/client/src/main/java/io/split/client/dtos/BasicCredentialsProvider.java
new file mode 100644
index 00000000..b77c9f59
--- /dev/null
+++ b/client/src/main/java/io/split/client/dtos/BasicCredentialsProvider.java
@@ -0,0 +1,7 @@
+package io.split.client.dtos;
+
+public interface BasicCredentialsProvider extends ProxyCredentialsProvider
+{
+ String getUsername();
+ String getPassword();
+}
diff --git a/client/src/main/java/io/split/client/dtos/BearerCredentialsProvider.java b/client/src/main/java/io/split/client/dtos/BearerCredentialsProvider.java
new file mode 100644
index 00000000..d4e98c5f
--- /dev/null
+++ b/client/src/main/java/io/split/client/dtos/BearerCredentialsProvider.java
@@ -0,0 +1,6 @@
+package io.split.client.dtos;
+
+public interface BearerCredentialsProvider extends ProxyCredentialsProvider
+{
+ String getToken();
+}
diff --git a/client/src/main/java/io/split/client/dtos/ProxyConfiguration.java b/client/src/main/java/io/split/client/dtos/ProxyConfiguration.java
new file mode 100644
index 00000000..93d58c4c
--- /dev/null
+++ b/client/src/main/java/io/split/client/dtos/ProxyConfiguration.java
@@ -0,0 +1,65 @@
+package io.split.client.dtos;
+
+import org.apache.hc.core5.http.HttpHost;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+public class ProxyConfiguration {
+ private final HttpHost _proxyHost;
+ private ProxyCredentialsProvider _provider;
+ private final String _p12File;
+ private final String _passKey;
+
+ private ProxyConfiguration(HttpHost proxyHost,
+ ProxyCredentialsProvider proxyCredentialsProvider,
+ String p12File, String passKey) {
+ _proxyHost = proxyHost;
+ _p12File = p12File;
+ _passKey = passKey;
+ _provider = proxyCredentialsProvider;
+ }
+
+ public HttpHost getHost() { return _proxyHost; }
+ public String getP12File() { return _p12File; }
+ public String getPassKey() { return _passKey; }
+ public ProxyCredentialsProvider getProxyCredentialsProvider() { return _provider; }
+
+ public static ProxyConfiguration.Builder builder() {
+ return new ProxyConfiguration.Builder();
+ }
+
+ public static class Builder {
+ private ProxyCredentialsProvider _provider;
+ private HttpHost _proxyHost;
+ private String _p12File;
+ private String _passKey;
+
+ public ProxyConfiguration.Builder credentialsProvider(ProxyCredentialsProvider provider) {
+ _provider = provider;
+ return this;
+ }
+
+ public ProxyConfiguration.Builder url(URL url) throws MalformedURLException {
+ try {
+ _proxyHost = new HttpHost(url.getProtocol(), url.getHost(), url.getPort());
+ } catch (Exception exc) {
+ throw new MalformedURLException("Proxy configuration is invalid. The proxy `url` is malformed");
+ }
+ return this;
+ }
+
+ public ProxyConfiguration.Builder mtls(String p12File, String passKey) {
+ _passKey = passKey;
+ _p12File = p12File;
+ return this;
+ }
+
+ public ProxyConfiguration build() {
+ if (_proxyHost == null) {
+ throw new IllegalArgumentException("Proxy configuration is invalid. The proxy `url` was not provided");
+ }
+ return new ProxyConfiguration(_proxyHost, _provider, _p12File, _passKey);
+ }
+ }
+}
\ No newline at end of file
diff --git a/client/src/main/java/io/split/client/dtos/ProxyCredentialsProvider.java b/client/src/main/java/io/split/client/dtos/ProxyCredentialsProvider.java
new file mode 100644
index 00000000..e1653d5f
--- /dev/null
+++ b/client/src/main/java/io/split/client/dtos/ProxyCredentialsProvider.java
@@ -0,0 +1,4 @@
+package io.split.client.dtos;
+
+public interface ProxyCredentialsProvider
+{}
diff --git a/client/src/test/java/io/split/client/SplitClientConfigTest.java b/client/src/test/java/io/split/client/SplitClientConfigTest.java
index 1b640071..8330f13a 100644
--- a/client/src/test/java/io/split/client/SplitClientConfigTest.java
+++ b/client/src/test/java/io/split/client/SplitClientConfigTest.java
@@ -1,6 +1,9 @@
package io.split.client;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import io.split.client.dtos.BasicCredentialsProvider;
+import io.split.client.dtos.BearerCredentialsProvider;
+import io.split.client.dtos.ProxyConfiguration;
import io.split.client.impressions.Impression;
import io.split.client.impressions.ImpressionListener;
import io.split.client.impressions.ImpressionsManager;
@@ -10,6 +13,8 @@
import org.junit.Test;
import org.mockito.Mockito;
+import java.net.MalformedURLException;
+import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -252,6 +257,107 @@ public Map> getHeaderOverrides(RequestContext context) {
SplitClientConfig config2 = SplitClientConfig.builder().build();
Assert.assertNull(config2.customHeaderDecorator());
+ }
+
+ @Test
+ public void checkProxyParams() throws MalformedURLException {
+ SplitClientConfig config = SplitClientConfig.builder()
+ .proxyConfiguration(new ProxyConfiguration.Builder()
+ .url(new URL("https://proxy-host:8888"))
+ .build())
+ .build();
+ Assert.assertEquals("proxy-host", config.proxyConfiguration().getHost().getHostName());
+ Assert.assertEquals(8888, config.proxyConfiguration().getHost().getPort());
+ Assert.assertEquals("https", config.proxyConfiguration().getHost().getSchemeName());
+
+ config = SplitClientConfig.builder()
+ .proxyConfiguration(new ProxyConfiguration.Builder()
+ .url(new URL("https://proxy-host:888"))
+ .credentialsProvider(new io.split.client.dtos.BasicCredentialsProvider() {
+ @Override
+ public String getUsername() {
+ return "user";
+ }
+
+ @Override
+ public String getPassword() {
+ return "pass";
+ }
+ })
+ .build())
+ .build();
+ io.split.client.dtos.BasicCredentialsProvider basicAuth = (io.split.client.dtos.BasicCredentialsProvider) config.proxyConfiguration().getProxyCredentialsProvider();
+ Assert.assertEquals("user", basicAuth.getUsername());
+ Assert.assertEquals("pass", basicAuth.getPassword());
+
+ io.split.client.dtos.BearerCredentialsProvider bearerCredentialsProvider = new io.split.client.dtos.BearerCredentialsProvider() {
+ @Override
+ public String getToken() {
+ return "my-token";
+ }
+ };
+
+ config = SplitClientConfig.builder()
+ .proxyConfiguration(new ProxyConfiguration.Builder()
+ .url(new URL("https://proxy-host:888"))
+ .credentialsProvider(bearerCredentialsProvider)
+ .build())
+ .build();
+ Assert.assertEquals(bearerCredentialsProvider, config.proxyConfiguration().getProxyCredentialsProvider());
+
+ config = SplitClientConfig.builder()
+ .proxyConfiguration(new ProxyConfiguration.Builder()
+ .url(new URL("https://proxy-host:888"))
+ .mtls("path/to/file", "pass-key")
+ .build())
+ .build();
+ Assert.assertEquals("path/to/file", config.proxyConfiguration().getP12File());
+ Assert.assertEquals("pass-key", config.proxyConfiguration().getPassKey());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void cannotUseInvalidHttpScheme() throws MalformedURLException {
+ SplitClientConfig.builder()
+ .proxyConfiguration(new ProxyConfiguration.Builder()
+ .url(new URL("ftp://proxy-host:888"))
+ .build())
+ .build();
+ }
+
+ @Test(expected = MalformedURLException.class)
+ public void cannotUseInvalidUrl() throws MalformedURLException {
+ SplitClientConfig.builder()
+ .proxyConfiguration(new ProxyConfiguration.Builder()
+ .url(new URL(""))
+ .build())
+ .build();
+ }
+ @Test(expected = IllegalArgumentException.class)
+ public void mustUseUrl() throws MalformedURLException {
+ SplitClientConfig.builder()
+ .proxyConfiguration(new ProxyConfiguration.Builder()
+ .build())
+ .build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void mustUseP12FileWithProxyMtls() throws MalformedURLException {
+ SplitClientConfig.builder()
+ .proxyConfiguration(new ProxyConfiguration.Builder()
+ .url(new URL("https://proxy-host:888"))
+ .mtls(null, "pass-key")
+ .build())
+ .build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void mustUseP12PassKeyWithProxyMtls() throws MalformedURLException {
+ SplitClientConfig.builder()
+ .proxyConfiguration(new ProxyConfiguration.Builder()
+ .url(new URL("https://proxy-host:888"))
+ .mtls("path/to/file", null)
+ .build())
+ .build();
}
}
\ No newline at end of file
diff --git a/client/src/test/java/io/split/client/SplitFactoryImplTest.java b/client/src/test/java/io/split/client/SplitFactoryImplTest.java
index a6da1069..222ddc82 100644
--- a/client/src/test/java/io/split/client/SplitFactoryImplTest.java
+++ b/client/src/test/java/io/split/client/SplitFactoryImplTest.java
@@ -1,16 +1,25 @@
package io.split.client;
+import io.split.client.dtos.ProxyConfiguration;
import io.split.client.impressions.ImpressionsManager;
import io.split.client.utils.FileTypeEnum;
import io.split.integrations.IntegrationsConfig;
+import io.split.service.SplitHttpClientImpl;
import io.split.storages.enums.OperationMode;
import io.split.storages.pluggable.domain.UserStorageWrapper;
import io.split.telemetry.storage.TelemetryStorage;
import io.split.telemetry.synchronizer.TelemetrySynchronizer;
import junit.framework.TestCase;
+import org.apache.hc.client5.http.auth.AuthScope;
+import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
+import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
+import org.apache.hc.client5.http.impl.io.DefaultHttpClientConnectionOperator;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
+import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.config.Registry;
import org.awaitility.Awaitility;
import org.junit.Assert;
-import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mockito;
import static org.mockito.Mockito.when;
@@ -24,6 +33,9 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@@ -87,24 +99,205 @@ public void testFactoryInstantiationIntegrationsConfig() throws Exception {
}
@Test
- public void testFactoryInstantiationWithProxy() throws Exception {
+ public void testFactoryInstantiationWithLegacyProxy() throws Exception {
SplitClientConfig splitClientConfig = SplitClientConfig.builder()
.enableDebug()
.impressionsMode(ImpressionsManager.Mode.DEBUG)
.impressionsRefreshRate(1)
- .endpoint(ENDPOINT,EVENTS_ENDPOINT)
+ .endpoint(ENDPOINT, EVENTS_ENDPOINT)
.telemetryURL(SplitClientConfig.TELEMETRY_ENDPOINT)
.authServiceURL(AUTH_SERVICE)
.setBlockUntilReadyTimeout(1000)
- .proxyPort(6060)
- .proxyUsername("test")
- .proxyPassword("password")
- .proxyHost(ENDPOINT)
+ .proxyPort(8888)
+ .proxyHost("proxy-host")
+ .proxyUsername("user")
+ .proxyPassword("pass")
.build();
SplitFactoryImpl splitFactory = new SplitFactoryImpl(API_KEY, splitClientConfig);
+ assertNotNull(splitFactory.client());
+
+ splitFactory.destroy();
+ }
+ @Test
+ public void testFactoryInstantiationWithProxyCredentials() throws Exception {
+ class MyBearerCredentialsProvider implements io.split.client.dtos.BasicCredentialsProvider {
+ @Override
+ public String getUsername() {
+ return "test";
+ }
+ @Override
+ public String getPassword() {
+ return "password";
+ }
+ };
+
+ SplitClientConfig splitClientConfig = SplitClientConfig.builder()
+ .enableDebug()
+ .impressionsMode(ImpressionsManager.Mode.DEBUG)
+ .impressionsRefreshRate(1)
+ .endpoint(ENDPOINT, EVENTS_ENDPOINT)
+ .telemetryURL(SplitClientConfig.TELEMETRY_ENDPOINT)
+ .authServiceURL(AUTH_SERVICE)
+ .setBlockUntilReadyTimeout(1000)
+ .proxyConfiguration(ProxyConfiguration.builder()
+ .url(new URL("http://proxy-name:6060"))
+ .credentialsProvider(new MyBearerCredentialsProvider())
+ .build())
+ .build();
+ SplitFactoryImpl splitFactory = new SplitFactoryImpl(API_KEY, splitClientConfig);
assertNotNull(splitFactory.client());
assertNotNull(splitFactory.manager());
+
+ Field splitHttpClientField = SplitFactoryImpl.class.getDeclaredField("_splitHttpClient");
+ splitHttpClientField.setAccessible(true);
+ SplitHttpClientImpl client = (SplitHttpClientImpl) splitHttpClientField.get(splitFactory);
+
+ Field httpClientField = SplitHttpClientImpl.class.getDeclaredField("_client");
+ httpClientField.setAccessible(true);
+ Class> InternalHttp = Class.forName("org.apache.hc.client5.http.impl.classic.InternalHttpClient");
+
+ Field routePlannerField = InternalHttp.getDeclaredField("routePlanner");
+ routePlannerField.setAccessible(true);
+ DefaultProxyRoutePlanner routePlanner = (DefaultProxyRoutePlanner) routePlannerField.get(InternalHttp.cast(httpClientField.get(client)));
+
+ Field proxyField = DefaultProxyRoutePlanner.class.getDeclaredField("proxy");
+ proxyField.setAccessible(true);
+ HttpHost proxy = (HttpHost) proxyField.get(routePlanner);
+
+ Assert.assertEquals("http", proxy.getSchemeName());
+ Assert.assertEquals("proxy-name", proxy.getHostName());
+ Assert.assertEquals(6060, proxy.getPort());
+
+ Field credentialsProviderField = InternalHttp.getDeclaredField("credentialsProvider");
+ credentialsProviderField.setAccessible(true);
+ BasicCredentialsProvider credentialsProvider = (BasicCredentialsProvider) credentialsProviderField.get(InternalHttp.cast(httpClientField.get(client)));
+
+ Field credMapField = BasicCredentialsProvider.class.getDeclaredField("credMap");
+ credMapField.setAccessible(true);
+ ConcurrentHashMap credMap = (ConcurrentHashMap) credMapField.get(credentialsProvider);
+
+ Assert.assertEquals("test", credMap.entrySet().stream().iterator().next().getValue().getUserName());
+ assertNotNull(credMap.entrySet().stream().iterator().next().getValue().getUserPassword());
+
+ splitFactory.destroy();
+ }
+
+ @Test
+ public void testFactoryInstantiationWithProxyToken() throws Exception {
+ class MyBearerCredentialsProvider implements io.split.client.dtos.BearerCredentialsProvider {
+ @Override
+ public String getToken() {
+ return "123456789";
+ }
+ };
+
+ SplitClientConfig splitClientConfig = SplitClientConfig.builder()
+ .enableDebug()
+ .impressionsMode(ImpressionsManager.Mode.DEBUG)
+ .impressionsRefreshRate(1)
+ .endpoint(ENDPOINT, EVENTS_ENDPOINT)
+ .telemetryURL(SplitClientConfig.TELEMETRY_ENDPOINT)
+ .authServiceURL(AUTH_SERVICE)
+ .setBlockUntilReadyTimeout(1000)
+ .proxyConfiguration(ProxyConfiguration.builder()
+ .url(new URL("http://proxy-name:6060"))
+ .credentialsProvider(new MyBearerCredentialsProvider())
+ .build())
+ .build();
+ SplitFactoryImpl splitFactory2 = new SplitFactoryImpl(API_KEY, splitClientConfig);
+ assertNotNull(splitFactory2.client());
+ assertNotNull(splitFactory2.manager());
+
+ Field splitHttpClientField2 = SplitFactoryImpl.class.getDeclaredField("_splitHttpClient");
+ splitHttpClientField2.setAccessible(true);
+ SplitHttpClientImpl client2 = (SplitHttpClientImpl) splitHttpClientField2.get(splitFactory2);
+
+ Field httpClientField2 = SplitHttpClientImpl.class.getDeclaredField("_client");
+ httpClientField2.setAccessible(true);
+ Class> InternalHttp2 = Class.forName("org.apache.hc.client5.http.impl.classic.InternalHttpClient");
+
+ Field credentialsProviderField2 = InternalHttp2.getDeclaredField("credentialsProvider");
+ credentialsProviderField2.setAccessible(true);
+ HttpClientDynamicCredentials credentialsProvider2 = (HttpClientDynamicCredentials) credentialsProviderField2.get(InternalHttp2.cast(httpClientField2.get(client2)));
+
+ Field proxyRuntimeField = HttpClientDynamicCredentials.class.getDeclaredField("_bearerCredentialsProvider");
+ proxyRuntimeField.setAccessible(true);
+ MyBearerCredentialsProvider proxyRuntime = (MyBearerCredentialsProvider) proxyRuntimeField.get(credentialsProvider2);
+
+ assertNotNull("123456789", proxyRuntime.getToken());
+
+ splitFactory2.destroy();
+ }
+
+ @Test
+ public void testFactoryInstantiationWithProxyMtls() throws Exception {
+ SplitClientConfig splitClientConfig = SplitClientConfig.builder()
+ .enableDebug()
+ .impressionsMode(ImpressionsManager.Mode.DEBUG)
+ .impressionsRefreshRate(1)
+ .endpoint(ENDPOINT,EVENTS_ENDPOINT)
+ .telemetryURL(SplitClientConfig.TELEMETRY_ENDPOINT)
+ .authServiceURL(AUTH_SERVICE)
+ .setBlockUntilReadyTimeout(1000)
+ .proxyConfiguration(ProxyConfiguration.builder()
+ .url(new URL("http://proxy-name:6060"))
+ .mtls("src/test/resources/keyStore.p12", "split")
+ .build())
+ .build();
+ SplitFactoryImpl splitFactory3 = new SplitFactoryImpl(API_KEY, splitClientConfig);
+ assertNotNull(splitFactory3.client());
+ assertNotNull(splitFactory3.manager());
+
+ Field splitHttpClientField3 = SplitFactoryImpl.class.getDeclaredField("_splitHttpClient");
+ splitHttpClientField3.setAccessible(true);
+ SplitHttpClientImpl client3 = (SplitHttpClientImpl) splitHttpClientField3.get(splitFactory3);
+
+ Field httpClientField3 = SplitHttpClientImpl.class.getDeclaredField("_client");
+ httpClientField3.setAccessible(true);
+ Class> InternalHttp3 = Class.forName("org.apache.hc.client5.http.impl.classic.InternalHttpClient");
+
+ Field connManagerField = InternalHttp3.getDeclaredField("connManager");
+ connManagerField.setAccessible(true);
+ PoolingHttpClientConnectionManager connManager = (PoolingHttpClientConnectionManager) connManagerField.get(InternalHttp3.cast(httpClientField3.get(client3)));
+
+ Field connectionOperatorField = PoolingHttpClientConnectionManager.class.getDeclaredField("connectionOperator");
+ connectionOperatorField.setAccessible(true);
+ DefaultHttpClientConnectionOperator connectionOperator = (DefaultHttpClientConnectionOperator) connectionOperatorField.get(connManager);
+
+ Field tlsSocketStrategyLookupField = DefaultHttpClientConnectionOperator.class.getDeclaredField("tlsSocketStrategyLookup");
+ tlsSocketStrategyLookupField.setAccessible(true);
+ Registry tlsSocketStrategyLookup = (Registry) tlsSocketStrategyLookupField.get(connectionOperator);
+
+ Field mapField = Registry.class.getDeclaredField("map");
+ mapField.setAccessible(true);
+ Class> map = mapField.get(tlsSocketStrategyLookup).getClass();
+
+ Class> value = ((ConcurrentHashMap) map.cast(mapField.get(tlsSocketStrategyLookup))).get("https").getClass();
+
+ Field arg1Field = value.getDeclaredField("arg$1");
+ arg1Field.setAccessible(true);
+ Class> sslConnectionSocketFactory = arg1Field.get(((ConcurrentHashMap) map.cast(mapField.get(tlsSocketStrategyLookup))).get("https")).getClass();
+
+ Field socketFactoryField = sslConnectionSocketFactory.getDeclaredField("socketFactory");
+ socketFactoryField.setAccessible(true);
+ Class> socketFactory = socketFactoryField.get(arg1Field.get(((ConcurrentHashMap) map.cast(mapField.get(tlsSocketStrategyLookup))).get("https"))).getClass();
+
+ Field contextField = socketFactory.getDeclaredField("context");
+ contextField.setAccessible(true);
+ Class> context = Class.forName("sun.security.ssl.SSLContextImpl");
+
+ Field keyManagerField = context.getDeclaredField("keyManager");
+ keyManagerField.setAccessible(true);
+ Class> keyManager = keyManagerField.get(contextField.get(socketFactoryField.get(arg1Field.get(((ConcurrentHashMap) map.cast(mapField.get(tlsSocketStrategyLookup))).get("https"))))).getClass();
+
+ Field credentialsMapField = keyManager.getDeclaredField("credentialsMap");
+ credentialsMapField.setAccessible(true);
+ HashMap credentialsMap = (HashMap) credentialsMapField.get(keyManagerField.get(contextField.get(socketFactoryField.get(arg1Field.get(((ConcurrentHashMap) map.cast(mapField.get(tlsSocketStrategyLookup))).get("https"))))));
+
+ assertNotNull(credentialsMap.get("1"));
+
+ splitFactory3.destroy();
}
@Test
diff --git a/client/src/test/resources/keyStore.p12 b/client/src/test/resources/keyStore.p12
new file mode 100644
index 00000000..ce2b3417
Binary files /dev/null and b/client/src/test/resources/keyStore.p12 differ
diff --git a/okhttp-modules/pom.xml b/okhttp-modules/pom.xml
index 3a842c81..0dfc2529 100644
--- a/okhttp-modules/pom.xml
+++ b/okhttp-modules/pom.xml
@@ -5,10 +5,10 @@
java-client-parent
io.split.client
- 4.16.0
+ 4.17.0-rc3
4.0.0
- 4.16.0
+ 4.17.0-rc3
okhttp-modules
jar
http-modules
@@ -25,7 +25,7 @@
0.8.0
true
- true
+ false
central
false
published
diff --git a/pluggable-storage/pom.xml b/pluggable-storage/pom.xml
index d5b1a995..3a908213 100644
--- a/pluggable-storage/pom.xml
+++ b/pluggable-storage/pom.xml
@@ -6,7 +6,7 @@
java-client-parent
io.split.client
- 4.16.0
+ 4.17.0-rc3
2.1.0
diff --git a/pom.xml b/pom.xml
index fe966220..e8acb3c8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
4.0.0
io.split.client
java-client-parent
- 4.16.0
+ 4.17.0-rc3
@@ -53,16 +53,8 @@
ossrh
https://oss.sonatype.org/content/repositories/releases
-
- maven-all-virtual
- https://splitio.jfrog.io/artifactory/maven-all-virtual
-
-
- maven-all-virtual
- https://splitio.jfrog.io/artifactory/maven-all-virtual
-
UTF-8
@@ -145,16 +137,6 @@
test
-
-
- maven-dev
- https://splitio.jfrog.io/artifactory/maven-dev/
-
-
- maven-dev
- https://splitio.jfrog.io/artifactory/maven-dev/
-
-
diff --git a/redis-wrapper/pom.xml b/redis-wrapper/pom.xml
index 64c5a115..b65a1984 100644
--- a/redis-wrapper/pom.xml
+++ b/redis-wrapper/pom.xml
@@ -6,7 +6,7 @@
java-client-parent
io.split.client
- 4.16.0
+ 4.17.0-rc3
redis-wrapper
3.1.1
diff --git a/testing/pom.xml b/testing/pom.xml
index de58f526..ee52481f 100644
--- a/testing/pom.xml
+++ b/testing/pom.xml
@@ -5,11 +5,11 @@
io.split.client
java-client-parent
- 4.16.0
+ 4.17.0-rc3
java-client-testing
jar
- 4.16.0
+ 4.17.0-rc3
Java Client For Testing
Testing suite for Java SDK for Split