From 2bc5767fbf5f3ce53db54d02b8e3376043a528e5 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Tue, 24 Jun 2025 13:06:12 -0700 Subject: [PATCH 1/2] Updated SplitClientConfig --- .../io/split/client/SplitClientConfig.java | 73 +++++++++++++++- .../io/split/client/dtos/ProxyMTLSAuth.java | 41 +++++++++ .../split/client/SplitClientConfigTest.java | 83 +++++++++++++++++++ 3 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 client/src/main/java/io/split/client/dtos/ProxyMTLSAuth.java diff --git a/client/src/main/java/io/split/client/SplitClientConfig.java b/client/src/main/java/io/split/client/SplitClientConfig.java index fd312c3b..f521cd33 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; @@ -85,6 +86,8 @@ public class SplitClientConfig { private final HttpHost _proxy; private final String _proxyUsername; private final String _proxyPassword; + private final String _proxyToken; + private final ProxyMTLSAuth _proxyMtlsAuth; // To be set during startup public static String splitSdkVersion; @@ -118,6 +121,8 @@ private SplitClientConfig(String endpoint, HttpHost proxy, String proxyUsername, String proxyPassword, + String proxyToken, + ProxyMTLSAuth proxyMtlsAuth, int eventsQueueSize, long eventSendIntervalInMillis, int maxStringLength, @@ -171,6 +176,8 @@ private SplitClientConfig(String endpoint, _proxy = proxy; _proxyUsername = proxyUsername; _proxyPassword = proxyPassword; + _proxyToken = proxyToken; + _proxyMtlsAuth = proxyMtlsAuth; _eventsQueueSize = eventsQueueSize; _eventSendIntervalInMillis = eventSendIntervalInMillis; _maxStringLength = maxStringLength; @@ -302,6 +309,14 @@ public String proxyPassword() { return _proxyPassword; } + public String proxyToken() { + return _proxyToken; + } + + public ProxyMTLSAuth proxyMTLSAuth() { + return _proxyMtlsAuth; + } + public long eventSendIntervalInMillis() { return _eventSendIntervalInMillis; } @@ -417,8 +432,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 +457,8 @@ public static final class Builder { private int _proxyPort = -1; private String _proxyUsername; private String _proxyPassword; + private String _proxyToken; + private ProxyMTLSAuth _proxyMtlsAuth; private int _eventsQueueSize = 500; private long _eventSendIntervalInMillis = 30 * (long)1000; private int _maxStringLength = 250; @@ -776,6 +793,28 @@ public Builder proxyPassword(String proxyPassword) { return this; } + /** + * Set the token for authentication against the proxy (if proxy settings are enabled). (Optional). + * + * @param proxyToken + * @return this builder + */ + public Builder proxyToken(String proxyToken) { + _proxyToken = proxyToken; + 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. * @@ -1096,6 +1135,34 @@ private void verifyAlternativeClient() { } } + private void verifyProxy() { + if (_proxyPort == -1) { + return; + } + + if (_proxyUsername == null && _proxyToken == null && _proxyMtlsAuth == null) { + return; + } + + if (_proxyUsername != null && _proxyToken != 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 (_proxyToken != null && _proxyMtlsAuth != null) { + throw new IllegalArgumentException("Proxy token and Proxy mTLS params are updated, set only one param."); + } + + if (_proxyMtlsAuth != null) { + if (_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 +1175,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 +1202,8 @@ public SplitClientConfig build() { proxy(), _proxyUsername, _proxyPassword, + _proxyToken, + _proxyMtlsAuth, _eventsQueueSize, _eventSendIntervalInMillis, _maxStringLength, 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..ddd59696 --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/ProxyMTLSAuth.java @@ -0,0 +1,41 @@ +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 Builder() { + } + + 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/test/java/io/split/client/SplitClientConfigTest.java b/client/src/test/java/io/split/client/SplitClientConfigTest.java index 1b640071..9e9688e1 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,88 @@ 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) + .proxyUsername("user") + .proxyPassword("pass") + .build(); + Assert.assertEquals("user", config.proxyUsername()); + Assert.assertEquals("pass", config.proxyPassword()); + config = SplitClientConfig.builder() + .proxyHost("proxy-host") + .proxyPort(8888) + .proxyToken("my-token") + .build(); + Assert.assertEquals("my-token", config.proxyToken()); + + 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 cannotUseProxyTokenAndProxyUsername() { + SplitClientConfig.builder() + .proxyHost("proxy-host") + .proxyPort(8888) + .proxyUsername("user") + .proxyPassword("pass") + .proxyToken("my-token") + .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() { + SplitClientConfig.builder() + .proxyHost("proxy-host") + .proxyPort(8888) + .proxyToken("my-token") + .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 From 3cca5ea15dbb7185128a54091a72e9cb614b92d3 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Tue, 24 Jun 2025 21:37:37 -0700 Subject: [PATCH 2/2] Added proxy scheme --- .../io/split/client/SplitClientConfig.java | 23 ++++++++++++++++++- .../split/client/SplitClientConfigTest.java | 10 ++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/client/src/main/java/io/split/client/SplitClientConfig.java b/client/src/main/java/io/split/client/SplitClientConfig.java index f521cd33..6c388b2d 100644 --- a/client/src/main/java/io/split/client/SplitClientConfig.java +++ b/client/src/main/java/io/split/client/SplitClientConfig.java @@ -35,6 +35,11 @@ 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 { + public static final String HTTP = "http"; + public static final String HTTPS = "https"; + } + private final String _endpoint; private final String _eventsEndpoint; @@ -455,6 +460,7 @@ 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 String _proxyToken; @@ -771,6 +777,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). * @@ -827,7 +844,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; @@ -1140,6 +1157,10 @@ private void verifyProxy() { return; } + if (!(_proxyScheme.equals(HttpScheme.HTTP) || _proxyScheme.equals(HttpScheme.HTTPS))) { + throw new IllegalArgumentException("Proxy scheme must be either http or https."); + } + if (_proxyUsername == null && _proxyToken == null && _proxyMtlsAuth == null) { return; } diff --git a/client/src/test/java/io/split/client/SplitClientConfigTest.java b/client/src/test/java/io/split/client/SplitClientConfigTest.java index 9e9688e1..ebb4803b 100644 --- a/client/src/test/java/io/split/client/SplitClientConfigTest.java +++ b/client/src/test/java/io/split/client/SplitClientConfigTest.java @@ -266,6 +266,7 @@ public void checkProxyParams() { config = SplitClientConfig.builder() .proxyHost("proxy-host") .proxyPort(8888) + .proxyScheme(SplitClientConfig.HttpScheme.HTTPS) .proxyUsername("user") .proxyPassword("pass") .build(); @@ -288,6 +289,15 @@ public void checkProxyParams() { 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() { SplitClientConfig.builder()