diff --git a/client/pom.xml b/client/pom.xml index 0fb3f854..d9c1629a 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -177,18 +177,6 @@ snakeyaml 2.0 - - com.squareup.okhttp3 - okhttp - 4.12.0 - true - - - com.squareup.okhttp3 - logging-interceptor - 4.12.0 - true - diff --git a/client/src/main/java/io/split/client/SplitClientConfig.java b/client/src/main/java/io/split/client/SplitClientConfig.java index 0a6b0fbd..effd2bb9 100644 --- a/client/src/main/java/io/split/client/SplitClientConfig.java +++ b/client/src/main/java/io/split/client/SplitClientConfig.java @@ -5,6 +5,7 @@ import io.split.client.utils.FileTypeEnum; import io.split.integrations.IntegrationsConfig; import io.split.service.ProxyAuthScheme; +import io.split.service.SplitHttpClient; import io.split.storages.enums.OperationMode; import io.split.storages.enums.StorageMode; import org.apache.hc.core5.http.HttpHost; @@ -94,6 +95,7 @@ public class SplitClientConfig { private final CustomHeaderDecorator _customHeaderDecorator; private final ProxyAuthScheme _proxyAuthScheme; private final String _proxyKerberosPrincipalName; + private final SplitHttpClient _proxyKerberosClient; public static Builder builder() { return new Builder(); @@ -152,7 +154,8 @@ private SplitClientConfig(String endpoint, int invalidSets, CustomHeaderDecorator customHeaderDecorator, ProxyAuthScheme proxyAuthScheme, - String proxyKerberosPrincipalName) { + String proxyKerberosPrincipalName, + SplitHttpClient proxyKerberosClient) { _endpoint = endpoint; _eventsEndpoint = eventsEndpoint; _featuresRefreshRate = pollForFeatureChangesEveryNSeconds; @@ -207,6 +210,7 @@ private SplitClientConfig(String endpoint, _customHeaderDecorator = customHeaderDecorator; _proxyAuthScheme = proxyAuthScheme; _proxyKerberosPrincipalName = proxyKerberosPrincipalName; + _proxyKerberosClient = proxyKerberosClient; Properties props = new Properties(); try { @@ -419,6 +423,7 @@ public ProxyAuthScheme proxyAuthScheme() { } public String proxyKerberosPrincipalName() { return _proxyKerberosPrincipalName; } + public SplitHttpClient proxyKerberosClient() { return _proxyKerberosClient; } public static final class Builder { private String _endpoint = SDK_ENDPOINT; @@ -478,6 +483,7 @@ public static final class Builder { private CustomHeaderDecorator _customHeaderDecorator = null; private ProxyAuthScheme _proxyAuthScheme = null; private String _proxyKerberosPrincipalName = null; + private SplitHttpClient _proxyKerberosClient = null; public Builder() { } @@ -994,6 +1000,17 @@ public Builder proxyKerberosPrincipalName(String proxyKerberosPrincipalName) { return this; } + /** + * Kerberos Http Client + * + * @param proxyKerberosClient + * @return this builder + */ + public Builder proxyKerberosClient(SplitHttpClient proxyKerberosClient) { + _proxyKerberosClient = proxyKerberosClient; + return this; + } + /** * Thread Factory * @@ -1060,6 +1077,9 @@ private void verifyAuthScheme() { if (_proxyKerberosPrincipalName == null) { throw new IllegalStateException("Kerberos mode require Kerberos Principal Name."); } + if (_proxyKerberosClient == null) { + throw new IllegalStateException("Kerberos mode require Kerberos Http Client."); + } } } @@ -1184,7 +1204,8 @@ public SplitClientConfig build() { _invalidSetsCount, _customHeaderDecorator, _proxyAuthScheme, - _proxyKerberosPrincipalName); + _proxyKerberosPrincipalName, + _proxyKerberosClient); } } } \ No newline at end of file diff --git a/client/src/main/java/io/split/client/SplitFactoryBuilder.java b/client/src/main/java/io/split/client/SplitFactoryBuilder.java index c2271ec4..2b48fb0d 100644 --- a/client/src/main/java/io/split/client/SplitFactoryBuilder.java +++ b/client/src/main/java/io/split/client/SplitFactoryBuilder.java @@ -2,6 +2,7 @@ import io.split.inputValidation.ApiKeyValidator; import io.split.grammar.Treatments; +import io.split.service.SplitHttpClient; import io.split.storages.enums.StorageMode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index 32568342..7595768d 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -58,10 +58,9 @@ import io.split.engine.segments.SegmentSynchronizationTaskImp; import io.split.integrations.IntegrationsConfig; import io.split.service.ProxyAuthScheme; -import io.split.service.SplitHttpClientKerberosImpl; import io.split.service.SplitHttpClientImpl; import io.split.service.SplitHttpClient; -import io.split.service.HTTPKerberosAuthInterceptor; + import io.split.storages.SegmentCache; import io.split.storages.SegmentCacheConsumer; import io.split.storages.SegmentCacheProducer; @@ -86,6 +85,7 @@ import io.split.telemetry.synchronizer.TelemetryInMemorySubmitter; import io.split.telemetry.synchronizer.TelemetrySyncTask; import io.split.telemetry.synchronizer.TelemetrySynchronizer; + import org.apache.hc.client5.http.auth.AuthScope; import org.apache.hc.client5.http.auth.Credentials; import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; @@ -108,26 +108,16 @@ import org.slf4j.LoggerFactory; import pluggable.CustomStorageWrapper; -import okhttp3.Authenticator; -import okhttp3.OkHttpClient; -import okhttp3.OkHttpClient.Builder; -import okhttp3.logging.HttpLoggingInterceptor; - import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.util.Map; -import java.util.HashMap; import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; import java.util.HashSet; import java.util.List; import java.util.ArrayList; -import java.util.concurrent.TimeUnit; import static io.split.client.utils.SplitExecutorFactory.buildExecutorService; @@ -167,15 +157,16 @@ public class SplitFactoryImpl implements SplitFactory { private final SplitSynchronizationTask _splitSynchronizationTask; private final EventsTask _eventsTask; private final SyncManager _syncManager; - private final SplitHttpClient _splitHttpClient; + private SplitHttpClient _splitHttpClient; private final UserStorageWrapper _userStorageWrapper; private final ImpressionsSender _impressionsSender; private final URI _rootTarget; private final URI _eventsRootTarget; private final UniqueKeysTracker _uniqueKeysTracker; + private RequestDecorator _requestDecorator; // Constructor for standalone mode - public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyntaxException, IOException { + public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyntaxException { _userStorageWrapper = null; _operationMode = config.operationMode(); _startTime = System.currentTimeMillis(); @@ -199,8 +190,14 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn _gates = new SDKReadinessGates(); // HttpClient - RequestDecorator requestDecorator = new RequestDecorator(config.customHeaderDecorator()); - _splitHttpClient = buildSplitHttpClient(apiToken, config, _sdkMetadata, requestDecorator); + _requestDecorator = new RequestDecorator(config.customHeaderDecorator()); + if (config.proxyAuthScheme() != ProxyAuthScheme.KERBEROS) { + _splitHttpClient = buildSplitHttpClient(apiToken, config, _sdkMetadata, _requestDecorator); + } else { + _splitHttpClient = config.proxyKerberosClient(); + _splitHttpClient.setMetaData(_sdkMetadata); + _splitHttpClient.setRequestDecorator(_requestDecorator); + } // Roots _rootTarget = URI.create(config.endpoint()); @@ -269,7 +266,7 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn // SyncManager SplitTasks splitTasks = SplitTasks.build(_splitSynchronizationTask, _segmentSynchronizationTaskImp, _impressionsManager, _eventsTask, _telemetrySyncTask, _uniqueKeysTracker); - SplitAPI splitAPI = SplitAPI.build(_splitHttpClient, buildSSEdHttpClient(apiToken, config, _sdkMetadata), requestDecorator); + SplitAPI splitAPI = SplitAPI.build(_splitHttpClient, buildSSEdHttpClient(apiToken, config, _sdkMetadata), _requestDecorator); _syncManager = SyncManagerImp.build(splitTasks, _splitFetcher, splitCache, splitAPI, segmentCache, _gates, _telemetryStorageProducer, _telemetrySynchronizer, config, splitParser, @@ -287,6 +284,14 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn } } + public RequestDecorator getRequestDecorator() { + return _requestDecorator; + } + + public SDKMetadata getSDKMetaData() { + return _sdkMetadata; + } + // Constructor for consumer mode protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStorageWrapper customStorageWrapper) throws URISyntaxException { @@ -503,36 +508,12 @@ public boolean isDestroyed() { return isTerminated; } + public void setSplitHttpClient(SplitHttpClient splitHttpClient) { + _splitHttpClient = splitHttpClient; + } protected static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClientConfig config, SDKMetadata sdkMetadata, RequestDecorator requestDecorator) - throws URISyntaxException, IOException { - // setup Kerberos client - if (config.proxyAuthScheme() == ProxyAuthScheme.KERBEROS) { - _log.info("Using Kerberos-Proxy Authentication Scheme."); - Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(config.proxy().getHostName(), config.proxy().getPort())); - HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); - if (config.debugEnabled()) { - logging.setLevel(HttpLoggingInterceptor.Level.HEADERS); - } else { - logging.setLevel(HttpLoggingInterceptor.Level.NONE); - } - - Map kerberosOptions = new HashMap<>(); - kerberosOptions.put("com.sun.security.auth.module.Krb5LoginModule", "required"); - kerberosOptions.put("refreshKrb5Config", "false"); - kerberosOptions.put("doNotPrompt", "false"); - kerberosOptions.put("useTicketCache", "true"); - - Authenticator proxyAuthenticator = getProxyAuthenticator(config, kerberosOptions); - OkHttpClient client = buildOkHttpClient(proxy, config, logging, proxyAuthenticator); - - return SplitHttpClientKerberosImpl.create( - client, - requestDecorator, - apiToken, - sdkMetadata); - } - + throws URISyntaxException { SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create() .setSslContext(SSLContexts.createSystemDefault()) .setTlsVersions(TLS.V_1_1, TLS.V_1_2) @@ -570,21 +551,6 @@ protected static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClie sdkMetadata); } - protected static OkHttpClient buildOkHttpClient(Proxy proxy, SplitClientConfig config, - HttpLoggingInterceptor logging, Authenticator proxyAuthenticator) { - return new Builder() - .proxy(proxy) - .readTimeout(config.readTimeout(), TimeUnit.MILLISECONDS) - .connectTimeout(config.connectionTimeout(), TimeUnit.MILLISECONDS) - .addInterceptor(logging) - .proxyAuthenticator(proxyAuthenticator) - .build(); - } - - protected static HTTPKerberosAuthInterceptor getProxyAuthenticator(SplitClientConfig config, - Map kerberosOptions) throws IOException { - return new HTTPKerberosAuthInterceptor(config.proxyKerberosPrincipalName(), kerberosOptions); - } private static CloseableHttpClient buildSSEdHttpClient(String apiToken, SplitClientConfig config, SDKMetadata sdkMetadata) { RequestConfig requestConfig = RequestConfig.custom() diff --git a/client/src/main/java/io/split/service/SplitHttpClient.java b/client/src/main/java/io/split/service/SplitHttpClient.java index 1c88bcd4..52026aaa 100644 --- a/client/src/main/java/io/split/service/SplitHttpClient.java +++ b/client/src/main/java/io/split/service/SplitHttpClient.java @@ -1,5 +1,7 @@ package io.split.service; +import io.split.client.RequestDecorator; +import io.split.client.utils.SDKMetadata; import io.split.engine.common.FetchOptions; import io.split.client.dtos.SplitHttpResponse; @@ -32,4 +34,8 @@ public interface SplitHttpClient extends Closeable { public SplitHttpResponse post(URI uri, HttpEntity entity, Map> additionalHeaders) throws IOException; -} + + public void setMetaData(SDKMetadata metadata); + + public void setRequestDecorator(RequestDecorator requestDecorator); +} \ No newline at end of file diff --git a/client/src/main/java/io/split/service/SplitHttpClientImpl.java b/client/src/main/java/io/split/service/SplitHttpClientImpl.java index 64ca3a55..af91400e 100644 --- a/client/src/main/java/io/split/service/SplitHttpClientImpl.java +++ b/client/src/main/java/io/split/service/SplitHttpClientImpl.java @@ -145,4 +145,15 @@ private void setBasicHeaders(HttpRequest request) { public void close() throws IOException { _client.close(); } + + @Override + public void setMetaData(SDKMetadata metadata) { + // only implemented for Kerberos client + } + + @Override + public void setRequestDecorator(RequestDecorator requestDecorator) { + // only implemented for Kerberos client + } + } diff --git a/client/src/test/java/io/split/service/HTTPKerberosAuthIntercepterTest.java b/client/src/test/java/io/split/service/HTTPKerberosAuthIntercepterTest.java deleted file mode 100644 index b49eda75..00000000 --- a/client/src/test/java/io/split/service/HTTPKerberosAuthIntercepterTest.java +++ /dev/null @@ -1,112 +0,0 @@ -package io.split.service; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -import javax.security.auth.Subject; -import javax.security.auth.kerberos.KerberosPrincipal; -import javax.security.auth.login.AppConfigurationEntry; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsEqual.equalTo; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.verify; -import static org.mockito.internal.verification.VerificationModeFactory.times; -import static org.powermock.api.mockito.PowerMockito.*; - -import java.security.PrivilegedActionException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -@RunWith(PowerMockRunner.class) -@PrepareForTest(HTTPKerberosAuthInterceptor.class) -public class HTTPKerberosAuthIntercepterTest { - - @Test - public void testBasicFlow() throws Exception { - System.setProperty("java.security.krb5.conf", "src/test/resources/krb5.conf"); - - HTTPKerberosAuthInterceptor kerberosAuthInterceptor = mock(HTTPKerberosAuthInterceptor.class); - LoginContext loginContext = PowerMockito.mock(LoginContext.class); - when(kerberosAuthInterceptor.getLoginContext(any())).thenReturn((loginContext)); - - doCallRealMethod().when(kerberosAuthInterceptor).buildSubjectCredentials(); - kerberosAuthInterceptor.buildSubjectCredentials(); - verify(loginContext, times(1)).login(); - - Subject subject = new Subject(); - when(loginContext.getSubject()).thenReturn(subject); - doCallRealMethod().when(kerberosAuthInterceptor).getContextSubject(); - kerberosAuthInterceptor.getContextSubject(); - verify(loginContext, times(1)).getSubject(); - - subject.getPrincipals().add(new KerberosPrincipal("bilal")); - subject.getPublicCredentials().add(new KerberosPrincipal("name")); - subject.getPrivateCredentials().add(new KerberosPrincipal("name")); - - doCallRealMethod().when(kerberosAuthInterceptor).getClientPrincipalName(); - assertThat(kerberosAuthInterceptor.getClientPrincipalName(), is(equalTo("bilal@ATHENA.MIT.EDU"))) ; - verify(loginContext, times(2)).getSubject(); - - when(kerberosAuthInterceptor.buildAuthorizationHeader(any())).thenReturn("secured-token"); - okhttp3.Request originalRequest = new okhttp3.Request.Builder().url("http://somthing").build(); - okhttp3.Response response = new okhttp3.Response.Builder().code(200).request(originalRequest). - protocol(okhttp3.Protocol.HTTP_1_1).message("ok").build(); - doCallRealMethod().when(kerberosAuthInterceptor).authenticate(null, response); - okhttp3.Request request = kerberosAuthInterceptor.authenticate(null, response); - assertThat(request.headers("Proxy-authorization"), is(equalTo(Arrays.asList("Negotiate secured-token")))); - } - - @Test - public void testKerberosLoginConfiguration() { - Map kerberosOptions = new HashMap(); - kerberosOptions.put("com.sun.security.auth.module.Krb5LoginModule", "required"); - kerberosOptions.put("refreshKrb5Config", "false"); - kerberosOptions.put("doNotPrompt", "false"); - kerberosOptions.put("useTicketCache", "true"); - - HTTPKerberosAuthInterceptor.KerberosLoginConfiguration kerberosConfig = new HTTPKerberosAuthInterceptor.KerberosLoginConfiguration(kerberosOptions); - AppConfigurationEntry[] appConfig = kerberosConfig.getAppConfigurationEntry(""); - assertThat("com.sun.security.auth.module.Krb5LoginModule", is(equalTo(appConfig[0].getLoginModuleName()))); - assertThat(AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, is(equalTo(appConfig[0].getControlFlag()))); - } - - @Test(expected = IllegalStateException.class) - public void testKerberosLoginConfigurationException() { - HTTPKerberosAuthInterceptor.KerberosLoginConfiguration kerberosConfig = new HTTPKerberosAuthInterceptor.KerberosLoginConfiguration(); - AppConfigurationEntry[] appConfig = kerberosConfig.getAppConfigurationEntry(""); - } - - @Test - public void testBuildAuthorizationHeader() throws LoginException, PrivilegedActionException { - System.setProperty("java.security.krb5.conf", "src/test/resources/krb5.conf"); - - HTTPKerberosAuthInterceptor kerberosAuthInterceptor = mock(HTTPKerberosAuthInterceptor.class); - HTTPKerberosAuthInterceptor.CreateAuthorizationHeaderAction ahh = mock(HTTPKerberosAuthInterceptor.CreateAuthorizationHeaderAction.class); - when(ahh.getNegotiateToken()).thenReturn("secret-token"); - when(kerberosAuthInterceptor.getAuthorizationHeaderAction(any(), any())).thenReturn(ahh); - - LoginContext loginContext = PowerMockito.mock(LoginContext.class); - doCallRealMethod().when(kerberosAuthInterceptor).buildAuthorizationHeader("bilal"); - Subject subject = new Subject(); - when(loginContext.getSubject()).thenReturn(subject); - when(kerberosAuthInterceptor.getContextSubject()).thenReturn(subject); - when(kerberosAuthInterceptor.getLoginContext(subject)).thenReturn((loginContext)); - doCallRealMethod().when(kerberosAuthInterceptor).buildSubjectCredentials(); - kerberosAuthInterceptor.buildSubjectCredentials(); - - subject.getPrincipals().add(new KerberosPrincipal("bilal")); - subject.getPublicCredentials().add(new KerberosPrincipal("name")); - subject.getPrivateCredentials().add(new KerberosPrincipal("name")); - doCallRealMethod().when(kerberosAuthInterceptor).getClientPrincipalName(); - - assertThat("secret-token", is(equalTo(kerberosAuthInterceptor.buildAuthorizationHeader("bilal")))); - } -} diff --git a/client/src/test/java/io/split/service/HttpSplitClientKerberosTest.java b/client/src/test/java/io/split/service/HttpSplitClientKerberosTest.java deleted file mode 100644 index 3ddf5c68..00000000 --- a/client/src/test/java/io/split/service/HttpSplitClientKerberosTest.java +++ /dev/null @@ -1,303 +0,0 @@ -package io.split.service; - -import com.google.common.base.Charsets; -import com.google.common.io.Files; - -import io.split.client.CustomHeaderDecorator; -import io.split.client.RequestDecorator; -import io.split.client.dtos.*; -import io.split.client.impressions.Impression; -import io.split.client.utils.Json; -import io.split.client.utils.SDKMetadata; -import io.split.client.utils.Utils; -import io.split.engine.common.FetchOptions; - -import okhttp3.OkHttpClient; -import okhttp3.OkHttpClient.*; -import okhttp3.HttpUrl; -import okhttp3.Headers; - -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; -import org.apache.hc.core5.http.*; -import org.apache.hc.core5.http.io.entity.EntityUtils; -import org.junit.Assert; -import org.junit.Test; - -import java.io.*; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.HttpURLConnection; -import java.net.Proxy; -import java.net.InetSocketAddress; -import java.util.List; -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsEqual.equalTo; - -public class HttpSplitClientKerberosTest { - - @Test - public void testGetWithSpecialCharacters() throws IOException, InterruptedException { - MockWebServer server = new MockWebServer(); - BufferedReader br = new BufferedReader(new FileReader("src/test/resources/split-change-special-characters.json")); - String body; - try { - StringBuilder sb = new StringBuilder(); - String line = br.readLine(); - - while (line != null) { - sb.append(line); - sb.append(System.lineSeparator()); - line = br.readLine(); - } - body = sb.toString(); - } finally { - br.close(); - } - - server.enqueue(new MockResponse().setBody(body).addHeader(HttpHeaders.VIA, "HTTP/1.1 s_proxy_rio1")); - server.start(); - HttpUrl baseUrl = server.url("/v1/"); - URI rootTarget = baseUrl.uri(); - RequestDecorator decorator = new RequestDecorator(null); - OkHttpClient client = new Builder().build(); - - SplitHttpClientKerberosImpl splitHttpClientKerberosImpl = new SplitHttpClientKerberosImpl(client, decorator, "qwerty", metadata()); - - Map> additionalHeaders = Collections.singletonMap("AdditionalHeader", - Collections.singletonList("add")); - - SplitHttpResponse splitHttpResponse = splitHttpClientKerberosImpl.get(rootTarget, - new FetchOptions.Builder().cacheControlHeaders(true).build(), additionalHeaders); - - - RecordedRequest request = server.takeRequest(); - server.shutdown(); - Headers requestHeaders = request.getHeaders(); - - assertThat(splitHttpResponse.statusCode(), is(equalTo(HttpURLConnection.HTTP_OK))); - Assert.assertEquals("/v1/", request.getPath()); - assertThat(requestHeaders.get("Authorization"), is(equalTo("Bearer qwerty"))) ; - assertThat(requestHeaders.get("SplitSDKClientKey"), is(equalTo("erty"))); - assertThat(requestHeaders.get("SplitSDKVersion"), is(equalTo("java-1.2.3"))); - assertThat(requestHeaders.get("SplitSDKMachineIP"), is(equalTo("1.2.3.4"))); - assertThat(requestHeaders.get("SplitSDKMachineName"), is(equalTo("someIP"))); - assertThat(requestHeaders.get("AdditionalHeader"), is(equalTo("add"))); - - SplitChange change = Json.fromJson(splitHttpResponse.body(), SplitChange.class); - Header[] headers = splitHttpResponse.responseHeaders(); - assertThat(headers[1].getName(), is(equalTo("via"))); - assertThat(headers[1].getValue(), is(equalTo("[HTTP/1.1 s_proxy_rio1]"))); - assertThat(splitHttpResponse.statusCode(), is(equalTo(200))); - Assert.assertNotNull(change); - Assert.assertEquals(1, change.splits.size()); - Assert.assertNotNull(change.splits.get(0)); - - Split split = change.splits.get(0); - Map configs = split.configurations; - Assert.assertEquals(2, configs.size()); - Assert.assertEquals("{\"test\": \"blue\",\"grüne Straße\": 13}", configs.get("on")); - Assert.assertEquals("{\"test\": \"blue\",\"size\": 15}", configs.get("off")); - Assert.assertEquals(2, split.sets.size()); - splitHttpClientKerberosImpl.close(); - } - - @Test - public void testGetErrors() throws IOException, InterruptedException { - MockWebServer server = new MockWebServer(); - server.enqueue(new MockResponse().setBody("").setResponseCode(HttpURLConnection.HTTP_INTERNAL_ERROR)); - server.start(); - HttpUrl baseUrl = server.url("/v1/"); - URI rootTarget = baseUrl.uri(); - RequestDecorator decorator = new RequestDecorator(null); - OkHttpClient client = new Builder().build(); - - SplitHttpClientKerberosImpl splitHttpClientKerberosImpl = new SplitHttpClientKerberosImpl(client, decorator, "qwerty", metadata()); - - Map> additionalHeaders = Collections.singletonMap("AdditionalHeader", - Collections.singletonList("add")); - - SplitHttpResponse splitHttpResponse = splitHttpClientKerberosImpl.get(rootTarget, - new FetchOptions.Builder().cacheControlHeaders(true).build(), additionalHeaders); - - - RecordedRequest request = server.takeRequest(); - server.shutdown(); - assertThat(splitHttpResponse.statusCode(), is(equalTo(HttpURLConnection.HTTP_INTERNAL_ERROR))); - splitHttpClientKerberosImpl.close(); - } - - - @Test - public void testGetParameters() throws IOException, InterruptedException { - class MyCustomHeaders implements CustomHeaderDecorator { - public MyCustomHeaders() {} - @Override - public Map> getHeaderOverrides(RequestContext context) { - Map> additionalHeaders = context.headers(); - additionalHeaders.put("first", Arrays.asList("1")); - additionalHeaders.put("second", Arrays.asList("2.1", "2.2")); - additionalHeaders.put("third", Arrays.asList("3")); - return additionalHeaders; - } - } - - MockWebServer server = new MockWebServer(); - BufferedReader br = new BufferedReader(new FileReader("src/test/resources/split-change-special-characters.json")); - String body; - try { - StringBuilder sb = new StringBuilder(); - String line = br.readLine(); - - while (line != null) { - sb.append(line); - sb.append(System.lineSeparator()); - line = br.readLine(); - } - body = sb.toString(); - } finally { - br.close(); - } - - server.enqueue(new MockResponse().setBody(body).addHeader(HttpHeaders.VIA, "HTTP/1.1 s_proxy_rio1")); - server.start(); - HttpUrl baseUrl = server.url("/splitChanges?since=1234567"); - URI rootTarget = baseUrl.uri(); - RequestDecorator decorator = new RequestDecorator(new MyCustomHeaders()); - OkHttpClient client = new Builder().build(); - - SplitHttpClientKerberosImpl splitHttpClientKerberosImpl = new SplitHttpClientKerberosImpl(client, decorator, "qwerty", metadata()); - - FetchOptions options = new FetchOptions.Builder().cacheControlHeaders(true).build(); - SplitHttpResponse splitHttpResponse = splitHttpClientKerberosImpl.get(rootTarget, options, null); - - RecordedRequest request = server.takeRequest(); - server.shutdown(); - Headers requestHeaders = request.getHeaders(); - - assertThat(requestHeaders.get("Cache-Control"), is(equalTo("no-cache"))); - assertThat(requestHeaders.get("first"), is(equalTo("1"))); - assertThat(requestHeaders.values("second"), is(equalTo(Arrays.asList("2.1","2.2")))); - assertThat(requestHeaders.get("third"), is(equalTo("3"))); - Assert.assertEquals("/splitChanges?since=1234567", request.getPath()); - assertThat(request.getMethod(), is(equalTo("GET"))); - } - - @Test(expected = IllegalStateException.class) - public void testException() throws URISyntaxException, IOException { - URI uri = new URI("https://api.split.io/splitChanges?since=1234567"); - RequestDecorator decorator = null; - - ByteArrayInputStream stubInputStream = new ByteArrayInputStream(Files.asCharSource( - new File("src/test/resources/split-change-special-characters.json"), Charsets.UTF_8).read().getBytes(Charsets.UTF_8)); - - Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("1.0.0.127", 8080)); - OkHttpClient client = new OkHttpClient.Builder() - .proxy(proxy) - .build(); - - SplitHttpClientKerberosImpl splitHtpClientKerberos = SplitHttpClientKerberosImpl.create(client, decorator, "qwerty", metadata()); - SplitHttpResponse splitHttpResponse = splitHtpClientKerberos.get(uri, - new FetchOptions.Builder().cacheControlHeaders(true).build(), null); - } - - @Test - public void testPost() throws IOException, ParseException, InterruptedException { - MockWebServer server = new MockWebServer(); - - server.enqueue(new MockResponse().addHeader(HttpHeaders.VIA, "HTTP/1.1 s_proxy_rio1")); - server.start(); - HttpUrl baseUrl = server.url("/impressions"); - URI rootTarget = baseUrl.uri(); - RequestDecorator decorator = new RequestDecorator(null); - OkHttpClient client = new Builder().build(); - - SplitHttpClientKerberosImpl splitHttpClientKerberosImpl = new SplitHttpClientKerberosImpl(client, decorator, "qwerty", metadata()); - - FetchOptions options = new FetchOptions.Builder().cacheControlHeaders(true).build(); - // Send impressions - List toSend = Arrays.asList(new TestImpressions("t1", Arrays.asList( - KeyImpression.fromImpression(new Impression("k1", null, "t1", "on", 123L, "r1", 456L, null)), - KeyImpression.fromImpression(new Impression("k2", null, "t1", "on", 123L, "r1", 456L, null)), - KeyImpression.fromImpression(new Impression("k3", null, "t1", "on", 123L, "r1", 456L, null)))), - new TestImpressions("t2", Arrays.asList( - KeyImpression.fromImpression(new Impression("k1", null, "t2", "on", 123L, "r1", 456L, null)), - KeyImpression.fromImpression(new Impression("k2", null, "t2", "on", 123L, "r1", 456L, null)), - KeyImpression.fromImpression(new Impression("k3", null, "t2", "on", 123L, "r1", 456L, null))))); - - Map> additionalHeaders = Collections.singletonMap("SplitSDKImpressionsMode", - Collections.singletonList("OPTIMIZED")); - - SplitHttpResponse splitHttpResponse = splitHttpClientKerberosImpl.post(rootTarget, Utils.toJsonEntity(toSend), - additionalHeaders); - - RecordedRequest request = server.takeRequest(); - server.shutdown(); - Headers requestHeaders = request.getHeaders(); - String postBody = EntityUtils.toString(Utils.toJsonEntity(toSend)); - - Assert.assertEquals("POST /impressions HTTP/1.1", request.getRequestLine()); - Assert.assertEquals(postBody, request.getBody().readUtf8()); - assertThat(requestHeaders.get("Authorization"), is(equalTo("Bearer qwerty"))) ; - assertThat(requestHeaders.get("SplitSDKClientKey"), is(equalTo("erty"))); - assertThat(requestHeaders.get("SplitSDKVersion"), is(equalTo("java-1.2.3"))); - assertThat(requestHeaders.get("SplitSDKMachineIP"), is(equalTo("1.2.3.4"))); - assertThat(requestHeaders.get("SplitSDKMachineName"), is(equalTo("someIP"))); - assertThat(requestHeaders.get("SplitSDKImpressionsMode"), is(equalTo("OPTIMIZED"))); - - Header[] headers = splitHttpResponse.responseHeaders(); - assertThat(headers[1].getName(), is(equalTo("via"))); - assertThat(headers[1].getValue(), is(equalTo("[HTTP/1.1 s_proxy_rio1]"))); - assertThat(splitHttpResponse.statusCode(), is(equalTo(200))); - } - - @Test - public void testPostErrors() throws IOException, InterruptedException { - MockWebServer server = new MockWebServer(); - server.enqueue(new MockResponse().setBody("").setResponseCode(HttpURLConnection.HTTP_INTERNAL_ERROR)); - server.start(); - HttpUrl baseUrl = server.url("/v1/"); - URI rootTarget = baseUrl.uri(); - RequestDecorator decorator = new RequestDecorator(null); - OkHttpClient client = new Builder().build(); - - SplitHttpClientKerberosImpl splitHttpClientKerberosImpl = new SplitHttpClientKerberosImpl(client, decorator, "qwerty", metadata()); - - Map> additionalHeaders = Collections.singletonMap("AdditionalHeader", - Collections.singletonList("add")); - - SplitHttpResponse splitHttpResponse = splitHttpClientKerberosImpl.post(rootTarget, - Utils.toJsonEntity("<>"), additionalHeaders); - - - RecordedRequest request = server.takeRequest(); - server.shutdown(); - assertThat(splitHttpResponse.statusCode(), is(equalTo(HttpURLConnection.HTTP_INTERNAL_ERROR))); - splitHttpClientKerberosImpl.close(); - } - - @Test(expected = IllegalStateException.class) - public void testPosttException() throws URISyntaxException { - RequestDecorator decorator = null; - URI uri = new URI("https://kubernetesturl.com/split/api/testImpressions/bulk"); - - Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("1.0.0.127", 8080)); - OkHttpClient client = new OkHttpClient.Builder() - .proxy(proxy) - .build(); - SplitHttpClientKerberosImpl splitHtpClientKerberos = SplitHttpClientKerberosImpl.create(client, decorator, "qwerty", metadata()); - SplitHttpResponse splitHttpResponse = splitHtpClientKerberos.post(uri, - Utils.toJsonEntity(Arrays.asList(new String[] { "A", "B", "C", "D" })), null); - } - - private SDKMetadata metadata() { - return new SDKMetadata("java-1.2.3", "1.2.3.4", "someIP"); - } - -} diff --git a/client/src/test/resources/krb5.conf b/client/src/test/resources/krb5.conf deleted file mode 100644 index 78d63ba8..00000000 --- a/client/src/test/resources/krb5.conf +++ /dev/null @@ -1,37 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -[libdefaults] - kdc_realm = ATHENA.MIT.EDU - default_realm = ATHENA.MIT.EDU - kdc_tcp_port = 88 - kdc_udp_port = 88 - dns_lookup_realm = false - dns_lookup_kdc = false - udp_preference_limit = 1 - -[logging] - default = FILE:/var/logs/krb5kdc.log - -[realms] - ATHENA.MIT.EDU = { -# kdc = 10.12.4.76:88 -# kdc = tcp/10.12.4.76:88 -# kdc = tcp/192.168.1.19:88 - kdc = 192.168.1.19:88 - } \ No newline at end of file diff --git a/kerberos/pom.xml b/kerberos/pom.xml new file mode 100644 index 00000000..461ac046 --- /dev/null +++ b/kerberos/pom.xml @@ -0,0 +1,90 @@ + + + + java-client-parent + io.split.client + 4.13.0 + + 4.0.0 + + kerberos + jar + Kerberos + Kerberos Authentication + + + + release + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.3 + true + + false + + + + + + + + + com.squareup.okhttp3 + okhttp + 4.12.0 + + + com.squareup.okhttp3 + logging-interceptor + 4.12.0 + + + org.apache.httpcomponents.client5 + httpclient5 + 5.0.3 + + + io.split.client + java-client + 4.13.0 + compile + + + + + junit + junit + test + + + org.mockito + mockito-core + 1.10.19 + test + + + org.powermock + powermock-module-junit4 + 1.7.4 + test + + + org.powermock + powermock-api-mockito + 1.7.4 + test + + + com.squareup.okhttp3 + mockwebserver + 4.8.0 + test + + + + \ No newline at end of file diff --git a/client/src/main/java/io/split/service/HTTPKerberosAuthInterceptor.java b/kerberos/src/main/java/io/split/kerberos/HTTPKerberosAuthInterceptor.java similarity index 99% rename from client/src/main/java/io/split/service/HTTPKerberosAuthInterceptor.java rename to kerberos/src/main/java/io/split/kerberos/HTTPKerberosAuthInterceptor.java index 038425c1..b72a8fef 100644 --- a/client/src/main/java/io/split/service/HTTPKerberosAuthInterceptor.java +++ b/kerberos/src/main/java/io/split/kerberos/HTTPKerberosAuthInterceptor.java @@ -1,6 +1,4 @@ -package io.split.service; - -import io.split.client.exceptions.KerberosAuthException; +package io.split.kerberos; import java.io.IOException; import java.util.Map; diff --git a/client/src/main/java/io/split/client/exceptions/KerberosAuthException.java b/kerberos/src/main/java/io/split/kerberos/KerberosAuthException.java similarity index 87% rename from client/src/main/java/io/split/client/exceptions/KerberosAuthException.java rename to kerberos/src/main/java/io/split/kerberos/KerberosAuthException.java index 462944d8..563bcf42 100644 --- a/client/src/main/java/io/split/client/exceptions/KerberosAuthException.java +++ b/kerberos/src/main/java/io/split/kerberos/KerberosAuthException.java @@ -1,4 +1,4 @@ -package io.split.client.exceptions; +package io.split.kerberos; public class KerberosAuthException extends Exception { public KerberosAuthException(String message) { diff --git a/kerberos/src/main/java/io/split/kerberos/SplitHttpClientKerberosBuilder.java b/kerberos/src/main/java/io/split/kerberos/SplitHttpClientKerberosBuilder.java new file mode 100644 index 00000000..11283e3d --- /dev/null +++ b/kerberos/src/main/java/io/split/kerberos/SplitHttpClientKerberosBuilder.java @@ -0,0 +1,56 @@ +package io.split.kerberos; + +import java.io.IOException; +import java.net.Proxy; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import okhttp3.Authenticator; +import okhttp3.OkHttpClient; +import okhttp3.OkHttpClient.Builder; +import okhttp3.logging.HttpLoggingInterceptor; + +public class SplitHttpClientKerberosBuilder { + private static final int DEFAULT_CONNECTION_TIMEOUT = 10000; + private static final int DEFAULT_READ_TIMEOUT = 10000; + + public static OkHttpClient buildOkHttpClient(Proxy proxy, String proxyKerberosPrincipalName, + boolean debugEnabled, int readTimeout, int connectionTimeout) throws IOException { + + HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); + if (debugEnabled) { + logging.setLevel(HttpLoggingInterceptor.Level.HEADERS); + } else { + logging.setLevel(HttpLoggingInterceptor.Level.NONE); + } + + if (connectionTimeout <= 0 || connectionTimeout > DEFAULT_CONNECTION_TIMEOUT) { + connectionTimeout = DEFAULT_CONNECTION_TIMEOUT; + } + if (readTimeout <= 0 || readTimeout > DEFAULT_READ_TIMEOUT) { + readTimeout = DEFAULT_READ_TIMEOUT; + } + + Map kerberosOptions = new HashMap<>(); + kerberosOptions.put("com.sun.security.auth.module.Krb5LoginModule", "required"); + kerberosOptions.put("refreshKrb5Config", "false"); + kerberosOptions.put("doNotPrompt", "false"); + kerberosOptions.put("useTicketCache", "true"); + + Authenticator proxyAuthenticator = getProxyAuthenticator(proxyKerberosPrincipalName, kerberosOptions); + + return new Builder() + .proxy(proxy) + .readTimeout(readTimeout, TimeUnit.MILLISECONDS) + .connectTimeout(connectionTimeout, TimeUnit.MILLISECONDS) + .addInterceptor(logging) + .proxyAuthenticator(proxyAuthenticator) + .build(); + } + + public static HTTPKerberosAuthInterceptor getProxyAuthenticator(String proxyKerberosPrincipalName, + Map kerberosOptions) throws IOException { + return new HTTPKerberosAuthInterceptor(proxyKerberosPrincipalName, kerberosOptions); + } +} diff --git a/client/src/main/java/io/split/service/SplitHttpClientKerberosImpl.java b/kerberos/src/main/java/io/split/kerberos/SplitHttpClientKerberosImpl.java similarity index 87% rename from client/src/main/java/io/split/service/SplitHttpClientKerberosImpl.java rename to kerberos/src/main/java/io/split/kerberos/SplitHttpClientKerberosImpl.java index ef5106e1..c29a84cb 100644 --- a/client/src/main/java/io/split/service/SplitHttpClientKerberosImpl.java +++ b/kerberos/src/main/java/io/split/kerberos/SplitHttpClientKerberosImpl.java @@ -1,16 +1,18 @@ -package io.split.service; +package io.split.kerberos; import io.split.client.RequestDecorator; import io.split.client.dtos.SplitHttpResponse; import io.split.client.utils.SDKMetadata; import io.split.engine.common.FetchOptions; +import io.split.service.SplitHttpClient; + +import split.org.apache.hc.client5.http.classic.methods.HttpGet; +import split.org.apache.hc.core5.http.Header; +import split.org.apache.hc.core5.http.HttpEntity; +import split.org.apache.hc.core5.http.HttpRequest; +import split.org.apache.hc.core5.http.io.entity.EntityUtils; +import split.org.apache.hc.core5.http.message.BasicHeader; -import org.apache.hc.client5.http.classic.methods.HttpGet; -import org.apache.hc.core5.http.Header; -import org.apache.hc.core5.http.HttpEntity; -import org.apache.hc.core5.http.HttpRequest; -import org.apache.hc.core5.http.io.entity.EntityUtils; -import org.apache.hc.core5.http.message.BasicHeader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,26 +40,33 @@ public class SplitHttpClientKerberosImpl implements SplitHttpClient { private static final String HEADER_CLIENT_MACHINE_IP = "SplitSDKMachineIP"; private static final String HEADER_CLIENT_VERSION = "SplitSDKVersion"; - private final RequestDecorator _requestDecorator; + private RequestDecorator _requestDecorator; private final String _apikey; - private final SDKMetadata _metadata; + private SDKMetadata _metadata; private final OkHttpClient _client; - public static SplitHttpClientKerberosImpl create(OkHttpClient client, RequestDecorator requestDecorator, - String apikey, - SDKMetadata metadata) { - return new SplitHttpClientKerberosImpl(client, requestDecorator, apikey, metadata); + public static SplitHttpClientKerberosImpl create(OkHttpClient client, + String apikey) { + return new SplitHttpClientKerberosImpl(client, apikey); } - SplitHttpClientKerberosImpl(OkHttpClient client, RequestDecorator requestDecorator, - String apikey, - SDKMetadata metadata) { - _requestDecorator = requestDecorator; + SplitHttpClientKerberosImpl(OkHttpClient client, + String apikey) { _apikey = apikey; - _metadata = metadata; _client = client; } + @Override + public void setMetaData(SDKMetadata metadata) { + _metadata = metadata; + } + + @Override + public void setRequestDecorator(RequestDecorator requestDecorator) { + _requestDecorator = requestDecorator; + } + + @Override public SplitHttpResponse get(URI uri, FetchOptions options, Map> additionalHeaders) { try { Builder requestBuilder = new Builder(); @@ -98,6 +107,7 @@ public SplitHttpResponse get(URI uri, FetchOptions options, Map> additionalHeaders) { try { @@ -107,7 +117,7 @@ public SplitHttpResponse post(URI url, HttpEntity entity, setAdditionalAndDecoratedHeaders(requestBuilder, additionalHeaders); requestBuilder.addHeader("Accept-Encoding", "gzip"); requestBuilder.addHeader("Content-Type", "application/json"); - String post = EntityUtils.toString(entity); + String post = EntityUtils.toString((HttpEntity) entity); RequestBody postBody = RequestBody.create(post.getBytes()); requestBuilder.post(postBody); @@ -177,7 +187,7 @@ protected Header[] getResponseHeaders(Response response) { responseHeaders.add(responseHeader); } } - return responseHeaders.toArray(new Header[0]); + return responseHeaders.toArray(new split.org.apache.hc.core5.http.Header[0]); } @Override public void close() throws IOException { diff --git a/pom.xml b/pom.xml index e99da05f..3f899b8e 100644 --- a/pom.xml +++ b/pom.xml @@ -85,6 +85,7 @@ redis-wrapper testing client + kerberos