diff --git a/client/pom.xml b/client/pom.xml index 1d4b32f4..5310cfea 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -5,9 +5,9 @@ io.split.client java-client-parent - 4.16.0 + 4.17.0-rc2 - 4.16.0 + 4.17.0-rc2 java-client jar Java Client @@ -24,7 +24,7 @@ 0.8.0 true - 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..01a32362 --- /dev/null +++ b/client/src/main/java/io/split/client/HttpClientDynamicCredentials.java @@ -0,0 +1,25 @@ +package io.split.client; + +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 ProxyCredentialsProvider _proxyCredentialsProvider; + + public HttpClientDynamicCredentials (ProxyCredentialsProvider proxyCredentialsProvider) { + _proxyCredentialsProvider = proxyCredentialsProvider; + } + + @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(_proxyCredentialsProvider.getJwtToken()); + } + +} + diff --git a/client/src/main/java/io/split/client/ProxyCredentialsProvider.java b/client/src/main/java/io/split/client/ProxyCredentialsProvider.java new file mode 100644 index 00000000..6fb595bc --- /dev/null +++ b/client/src/main/java/io/split/client/ProxyCredentialsProvider.java @@ -0,0 +1,10 @@ +package io.split.client; + +public interface ProxyCredentialsProvider +{ + /** + * Get the additional headers needed for all http operations + * @return HashMap of addition headers + */ + String getJwtToken(); +} diff --git a/client/src/main/java/io/split/client/SplitClientConfig.java b/client/src/main/java/io/split/client/SplitClientConfig.java index fd312c3b..d314baa7 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.ProxyMTLSAuth; import io.split.client.impressions.ImpressionListener; import io.split.client.impressions.ImpressionsManager; import io.split.client.utils.FileTypeEnum; @@ -34,6 +35,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; @@ -85,6 +94,8 @@ public class SplitClientConfig { private final HttpHost _proxy; private final String _proxyUsername; private final String _proxyPassword; + private final ProxyCredentialsProvider _proxyCredentialsProvider; + private final ProxyMTLSAuth _proxyMtlsAuth; // To be set during startup public static String splitSdkVersion; @@ -118,6 +129,8 @@ private SplitClientConfig(String endpoint, HttpHost proxy, String proxyUsername, String proxyPassword, + ProxyCredentialsProvider proxyCredentialsProvider, + ProxyMTLSAuth proxyMtlsAuth, int eventsQueueSize, long eventSendIntervalInMillis, int maxStringLength, @@ -171,6 +184,8 @@ private SplitClientConfig(String endpoint, _proxy = proxy; _proxyUsername = proxyUsername; _proxyPassword = proxyPassword; + _proxyCredentialsProvider = proxyCredentialsProvider; + _proxyMtlsAuth = proxyMtlsAuth; _eventsQueueSize = eventsQueueSize; _eventSendIntervalInMillis = eventSendIntervalInMillis; _maxStringLength = maxStringLength; @@ -302,6 +317,14 @@ public String proxyPassword() { return _proxyPassword; } + public ProxyCredentialsProvider proxyCredentialsProvider() { + return _proxyCredentialsProvider; + } + + public ProxyMTLSAuth proxyMTLSAuth() { + return _proxyMtlsAuth; + } + public long eventSendIntervalInMillis() { return _eventSendIntervalInMillis; } @@ -417,8 +440,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; @@ -440,8 +463,11 @@ public static final class Builder { private int _waitBeforeShutdown = 5000; private String _proxyHost = "localhost"; private int _proxyPort = -1; + private String _proxyScheme = HttpScheme.HTTP; private String _proxyUsername; private String _proxyPassword; + private ProxyCredentialsProvider _proxyCredentialsProvider; + private ProxyMTLSAuth _proxyMtlsAuth; private int _eventsQueueSize = 500; private long _eventSendIntervalInMillis = 30 * (long)1000; private int _maxStringLength = 250; @@ -754,6 +780,17 @@ public Builder proxyPort(int proxyPort) { return this; } + /** + * The http scheme of the proxy. Default is http. + * + * @param proxyScheme protocol for the proxy + * @return this builder + */ + public Builder proxyScheme(String proxyScheme) { + _proxyScheme = proxyScheme; + return this; + } + /** * Set the username for authentication against the proxy (if proxy settings are enabled). (Optional). * @@ -776,6 +813,28 @@ public Builder proxyPassword(String proxyPassword) { return this; } + /** + * Set the token for authentication against the proxy (if proxy settings are enabled). (Optional). + * + * @param proxyCredentialsProvider + * @return this builder + */ + public Builder proxyCredentialsProvider(ProxyCredentialsProvider proxyCredentialsProvider) { + _proxyCredentialsProvider = proxyCredentialsProvider; + return this; + } + + /** + * Set the mtls authentication against the proxy (if proxy settings are enabled). (Optional). + * + * @param proxyMtlsAuth + * @return this builder + */ + public Builder proxyMtlsAuth(ProxyMTLSAuth proxyMtlsAuth) { + _proxyMtlsAuth = proxyMtlsAuth; + return this; + } + /** * Disables running destroy() on shutdown by default. * @@ -788,7 +847,7 @@ public Builder disableDestroyOnShutDown() { HttpHost proxy() { if (_proxyPort != -1) { - return new HttpHost(_proxyHost, _proxyPort); + return new HttpHost(_proxyScheme, _proxyHost, _proxyPort); } // Default is no proxy. return null; @@ -1096,6 +1155,36 @@ private void verifyAlternativeClient() { } } + private void verifyProxy() { + if (_proxyPort == -1) { + return; + } + + if (!(_proxyScheme.equals(HttpScheme.HTTP) || _proxyScheme.equals(HttpScheme.HTTPS))) { + throw new IllegalArgumentException("Proxy scheme must be either http or https."); + } + + if (_proxyUsername == null && _proxyCredentialsProvider == null && _proxyMtlsAuth == null) { + return; + } + + if (_proxyUsername != null && _proxyCredentialsProvider != null) { + throw new IllegalArgumentException("Proxy user and Proxy token params are updated, set only one param."); + } + + if (_proxyUsername != null && _proxyMtlsAuth != null) { + throw new IllegalArgumentException("Proxy user and Proxy mTLS params are updated, set only one param."); + } + + if (_proxyCredentialsProvider != null && _proxyMtlsAuth != null) { + throw new IllegalArgumentException("Proxy token and Proxy mTLS params are updated, set only one param."); + } + + if (_proxyMtlsAuth != null && (_proxyMtlsAuth.getP12File() == null || _proxyMtlsAuth.getP12FilePassKey() == null)) { + throw new IllegalArgumentException("Proxy mTLS must have p12 file path and name, and pass phrase."); + } + } + public SplitClientConfig build() { verifyRates(); @@ -1108,6 +1197,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 +1224,8 @@ public SplitClientConfig build() { proxy(), _proxyUsername, _proxyPassword, + _proxyCredentialsProvider, + _proxyMtlsAuth, _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..6e9d9913 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -113,11 +113,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 +520,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(); @@ -556,13 +562,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(); @@ -589,6 +597,33 @@ private static CloseableHttpClient buildSSEdHttpClient(String apiToken, SplitCli return httpClientbuilder.build(); } + private static SSLContext buildSSLContext(SplitClientConfig config) throws IOException, NullPointerException { + SSLContext sslContext; + if (config.proxyMTLSAuth() != 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.proxyMTLSAuth().getP12File())); + keyStore.load(keystoreStream, config.proxyMTLSAuth().getP12FilePassKey().toCharArray()); + sslContext = SSLContexts.custom() + .loadKeyMaterial(keyStore, config.proxyMTLSAuth().getP12FilePassKey().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()); @@ -604,6 +639,11 @@ private static HttpClientBuilder setupProxy(HttpClientBuilder httpClientbuilder, httpClientbuilder.setDefaultCredentialsProvider(credsProvider); } + if (config.proxyCredentialsProvider() != null) { + _log.debug("Proxy setup using token"); + httpClientbuilder.setDefaultCredentialsProvider(new HttpClientDynamicCredentials(config.proxyCredentialsProvider())); + } + return httpClientbuilder; } diff --git a/client/src/main/java/io/split/client/dtos/ProxyMTLSAuth.java b/client/src/main/java/io/split/client/dtos/ProxyMTLSAuth.java new file mode 100644 index 00000000..5cca5abd --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/ProxyMTLSAuth.java @@ -0,0 +1,38 @@ +package io.split.client.dtos; + +public class ProxyMTLSAuth { + private final String _proxyP12File; + private final String _proxyP12FilePassKey; + + private ProxyMTLSAuth(String proxyP12File, String proxyP12FilePassKey) { + _proxyP12File = proxyP12File; + _proxyP12FilePassKey = proxyP12FilePassKey; + } + + public String getP12File() { return _proxyP12File; } + + public String getP12FilePassKey() { return _proxyP12FilePassKey; } + + public static ProxyMTLSAuth.Builder builder() { + return new ProxyMTLSAuth.Builder(); + } + + public static class Builder { + private String _p12File; + private String _p12FilePassKey; + + public ProxyMTLSAuth.Builder proxyP12File(String p12File) { + _p12File = p12File; + return this; + } + + public ProxyMTLSAuth.Builder proxyP12FilePassKey(String p12FilePassKey) { + _p12FilePassKey = p12FilePassKey; + return this; + } + + public ProxyMTLSAuth build() { + return new ProxyMTLSAuth(_p12File, _p12FilePassKey); + } + } +} \ No newline at end of file diff --git a/client/src/main/java/io/split/client/impressions/UniqueKeysTrackerImp.java b/client/src/main/java/io/split/client/impressions/UniqueKeysTrackerImp.java index bd2ff918..80b3703d 100644 --- a/client/src/main/java/io/split/client/impressions/UniqueKeysTrackerImp.java +++ b/client/src/main/java/io/split/client/impressions/UniqueKeysTrackerImp.java @@ -117,7 +117,7 @@ private void sendUniqueKeys(){ } try { if (uniqueKeysTracker.size() == 0) { - _log.warn("The Unique Keys Tracker is empty"); + _log.debug("The Unique Keys Tracker is empty"); return; } HashMap> uniqueKeysHashMap = popAll(); diff --git a/client/src/test/java/io/split/client/SplitClientConfigTest.java b/client/src/test/java/io/split/client/SplitClientConfigTest.java index 1b640071..81ad9913 100644 --- a/client/src/test/java/io/split/client/SplitClientConfigTest.java +++ b/client/src/test/java/io/split/client/SplitClientConfigTest.java @@ -1,6 +1,7 @@ package io.split.client; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import io.split.client.dtos.ProxyMTLSAuth; import io.split.client.impressions.Impression; import io.split.client.impressions.ImpressionListener; import io.split.client.impressions.ImpressionsManager; @@ -252,6 +253,119 @@ public Map> getHeaderOverrides(RequestContext context) { SplitClientConfig config2 = SplitClientConfig.builder().build(); Assert.assertNull(config2.customHeaderDecorator()); + } + + @Test + public void checkProxyParams() { + SplitClientConfig config = SplitClientConfig.builder() + .proxyHost("proxy-host") + .proxyPort(8888).build(); + Assert.assertEquals("proxy-host", config.proxy().getHostName()); + Assert.assertEquals(8888, config.proxy().getPort()); + + config = SplitClientConfig.builder() + .proxyHost("proxy-host") + .proxyPort(8888) + .proxyScheme(SplitClientConfig.HttpScheme.HTTPS) + .proxyUsername("user") + .proxyPassword("pass") + .build(); + Assert.assertEquals("user", config.proxyUsername()); + Assert.assertEquals("pass", config.proxyPassword()); + + ProxyCredentialsProvider proxyCredentialsProvider = new ProxyCredentialsProvider() { + @Override + public String getJwtToken() { + return "my-token"; + } + }; + + config = SplitClientConfig.builder() + .proxyHost("proxy-host") + .proxyPort(8888) + .proxyCredentialsProvider(proxyCredentialsProvider) + .build(); + Assert.assertEquals(proxyCredentialsProvider, config.proxyCredentialsProvider()); + + config = SplitClientConfig.builder() + .proxyHost("proxy-host") + .proxyPort(8888) + .proxyMtlsAuth(new ProxyMTLSAuth.Builder().proxyP12File("path/to/file").proxyP12FilePassKey("pass-key").build()) + .build(); + Assert.assertEquals("path/to/file", config.proxyMTLSAuth().getP12File()); + Assert.assertEquals("pass-key", config.proxyMTLSAuth().getP12FilePassKey()); + } + + @Test(expected = IllegalArgumentException.class) + public void cannotUseInvalidHttpScheme() { + SplitClientConfig.builder() + .proxyHost("proxy-host") + .proxyPort(8888) + .proxyScheme("ftp") + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void cannotUseProxyTokenAndProxyUsername() { + ProxyCredentialsProvider proxyCredentialsProvider = new ProxyCredentialsProvider() { + @Override + public String getJwtToken() { + return "my-token"; + } + }; + + SplitClientConfig.builder() + .proxyHost("proxy-host") + .proxyPort(8888) + .proxyUsername("user") + .proxyPassword("pass") + .proxyCredentialsProvider(proxyCredentialsProvider) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void cannotUseProxyUserAndProxyMtls() { + SplitClientConfig.builder() + .proxyHost("proxy-host") + .proxyPort(8888) + .proxyUsername("user") + .proxyPassword("pass") + .proxyMtlsAuth(new ProxyMTLSAuth.Builder().proxyP12File("path/to/file").proxyP12FilePassKey("pass-key").build()) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void cannotUseProxyTokenAndProxyMtls() { + ProxyCredentialsProvider proxyCredentialsProvider = new ProxyCredentialsProvider() { + @Override + public String getJwtToken() { + return "my-token"; + } + }; + SplitClientConfig.builder() + .proxyHost("proxy-host") + .proxyPort(8888) + .proxyCredentialsProvider(proxyCredentialsProvider) + .proxyMtlsAuth(new ProxyMTLSAuth.Builder().proxyP12File("path/to/file").proxyP12FilePassKey("pass-key").build()) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void mustUseP12FileWithProxyMtls() { + SplitClientConfig.builder() + .proxyHost("proxy-host") + .proxyPort(8888) + .proxyMtlsAuth(new ProxyMTLSAuth.Builder().proxyP12FilePassKey("pass-key").build()) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void mustUseP12PassKeyWithProxyMtls() { + SplitClientConfig.builder() + .proxyHost("proxy-host") + .proxyPort(8888) + .proxyMtlsAuth(new ProxyMTLSAuth.Builder().proxyP12File("path/to/file").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..1214b246 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.ProxyMTLSAuth; 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,8 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -87,12 +98,12 @@ public void testFactoryInstantiationIntegrationsConfig() throws Exception { } @Test - public void testFactoryInstantiationWithProxy() throws Exception { + public void testFactoryInstantiationWithProxyCredentials() 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) @@ -102,9 +113,157 @@ public void testFactoryInstantiationWithProxy() throws Exception { .proxyHost(ENDPOINT) .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(ENDPOINT, 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 MyProxyCredentialsProvider implements ProxyCredentialsProvider { + @Override + public String getJwtToken() { + 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) + .proxyPort(6060) + .proxyCredentialsProvider(new MyProxyCredentialsProvider()) + .proxyHost(ENDPOINT) + .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("_proxyCredentialsProvider"); + proxyRuntimeField.setAccessible(true); + MyProxyCredentialsProvider proxyRuntime = (MyProxyCredentialsProvider) proxyRuntimeField.get(credentialsProvider2); + + assertNotNull("123456789", proxyRuntime.getJwtToken()); + + 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) + .proxyPort(6060) + .proxyScheme("https") + .proxyMtlsAuth(new ProxyMTLSAuth.Builder().proxyP12File("src/test/resources/keyStore.p12").proxyP12FilePassKey("split").build()) + .proxyHost(ENDPOINT) + .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..ff0bac2e 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-rc2 4.0.0 - 4.16.0 + 4.17.0-rc2 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..0353d0c5 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-rc2 2.1.0 diff --git a/pom.xml b/pom.xml index fe966220..cabb7d91 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-rc2 diff --git a/redis-wrapper/pom.xml b/redis-wrapper/pom.xml index 64c5a115..0da0c71f 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-rc2 redis-wrapper 3.1.1 diff --git a/testing/pom.xml b/testing/pom.xml index de58f526..f7316b7b 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-rc2 java-client-testing jar - 4.16.0 + 4.17.0-rc2 Java Client For Testing Testing suite for Java SDK for Split