Skip to content

Commit e388362

Browse files
authored
Merge pull request #511 from splitio/kerberos-support
Kerberos support
2 parents 23074ab + 97bf31b commit e388362

File tree

12 files changed

+499
-7
lines changed

12 files changed

+499
-7
lines changed

client/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>io.split.client</groupId>
77
<artifactId>java-client-parent</artifactId>
8-
<version>4.12.1</version>
8+
<version>4.13.0-rc1</version>
99
</parent>
1010
<artifactId>java-client</artifactId>
1111
<packaging>jar</packaging>

client/src/main/java/io/split/client/SplitClientConfig.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.split.integrations.IntegrationsConfig;
77
import io.split.storages.enums.OperationMode;
88
import io.split.storages.enums.StorageMode;
9+
import io.split.service.HttpAuthScheme;
910
import org.apache.hc.core5.http.HttpHost;
1011
import pluggable.CustomStorageWrapper;
1112

@@ -91,6 +92,7 @@ public class SplitClientConfig {
9192
private final HashSet<String> _flagSetsFilter;
9293
private final int _invalidSets;
9394
private final CustomHeaderDecorator _customHeaderDecorator;
95+
private final HttpAuthScheme _authScheme;
9496

9597

9698
public static Builder builder() {
@@ -148,7 +150,8 @@ private SplitClientConfig(String endpoint,
148150
ThreadFactory threadFactory,
149151
HashSet<String> flagSetsFilter,
150152
int invalidSets,
151-
CustomHeaderDecorator customHeaderDecorator) {
153+
CustomHeaderDecorator customHeaderDecorator,
154+
HttpAuthScheme authScheme) {
152155
_endpoint = endpoint;
153156
_eventsEndpoint = eventsEndpoint;
154157
_featuresRefreshRate = pollForFeatureChangesEveryNSeconds;
@@ -201,6 +204,7 @@ private SplitClientConfig(String endpoint,
201204
_flagSetsFilter = flagSetsFilter;
202205
_invalidSets = invalidSets;
203206
_customHeaderDecorator = customHeaderDecorator;
207+
_authScheme = authScheme;
204208

205209
Properties props = new Properties();
206210
try {
@@ -408,6 +412,9 @@ public int getInvalidSets() {
408412
public CustomHeaderDecorator customHeaderDecorator() {
409413
return _customHeaderDecorator;
410414
}
415+
public HttpAuthScheme authScheme() {
416+
return _authScheme;
417+
}
411418

412419
public static final class Builder {
413420

@@ -466,6 +473,7 @@ public static final class Builder {
466473
private HashSet<String> _flagSetsFilter = new HashSet<>();
467474
private int _invalidSetsCount = 0;
468475
private CustomHeaderDecorator _customHeaderDecorator = null;
476+
private HttpAuthScheme _authScheme = null;
469477

470478
public Builder() {
471479
}
@@ -960,6 +968,17 @@ public Builder customHeaderDecorator(CustomHeaderDecorator customHeaderDecorator
960968
return this;
961969
}
962970

971+
/**
972+
* Authentication Scheme
973+
*
974+
* @param authScheme
975+
* @return this builder
976+
*/
977+
public Builder authScheme(HttpAuthScheme authScheme) {
978+
_authScheme = authScheme;
979+
return this;
980+
}
981+
963982
/**
964983
* Thread Factory
965984
*
@@ -1120,7 +1139,8 @@ public SplitClientConfig build() {
11201139
_threadFactory,
11211140
_flagSetsFilter,
11221141
_invalidSetsCount,
1123-
_customHeaderDecorator);
1142+
_customHeaderDecorator,
1143+
_authScheme);
11241144
}
11251145
}
11261146
}

client/src/main/java/io/split/client/SplitFactoryImpl.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,10 @@
5757
import io.split.engine.segments.SegmentChangeFetcher;
5858
import io.split.engine.segments.SegmentSynchronizationTaskImp;
5959
import io.split.integrations.IntegrationsConfig;
60+
import io.split.service.HttpAuthScheme;
6061
import io.split.service.SplitHttpClient;
6162
import io.split.service.SplitHttpClientImpl;
63+
import io.split.service.SplitHttpClientKerberosImpl;
6264
import io.split.storages.SegmentCache;
6365
import io.split.storages.SegmentCacheConsumer;
6466
import io.split.storages.SegmentCacheProducer;
@@ -525,6 +527,13 @@ private static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClient
525527
httpClientbuilder = setupProxy(httpClientbuilder, config);
526528
}
527529

530+
if (config.authScheme() == HttpAuthScheme.KERBEROS) {
531+
return SplitHttpClientKerberosImpl.create(
532+
requestDecorator,
533+
apiToken,
534+
sdkMetadata);
535+
536+
}
528537
return SplitHttpClientImpl.create(httpClientbuilder.build(),
529538
requestDecorator,
530539
apiToken,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package io.split.service;
2+
3+
public enum HttpAuthScheme {
4+
KERBEROS
5+
}
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package io.split.service;
2+
3+
import io.split.client.RequestDecorator;
4+
import io.split.client.dtos.SplitHttpResponse;
5+
import io.split.client.utils.SDKMetadata;
6+
import io.split.engine.common.FetchOptions;
7+
8+
import org.apache.hc.client5.http.classic.methods.HttpGet;
9+
import org.apache.hc.core5.http.Header;
10+
import org.apache.hc.core5.http.HttpEntity;
11+
import org.apache.hc.core5.http.HttpRequest;
12+
import org.apache.hc.core5.http.io.entity.EntityUtils;
13+
import org.apache.hc.core5.http.message.BasicHeader;
14+
import org.slf4j.Logger;
15+
import org.slf4j.LoggerFactory;
16+
17+
import java.io.InputStreamReader;
18+
import java.io.BufferedReader;
19+
import java.io.IOException;
20+
import java.io.OutputStream;
21+
import java.net.URI;
22+
import java.net.HttpURLConnection;
23+
import java.nio.charset.StandardCharsets;
24+
import java.util.ArrayList;
25+
import java.util.List;
26+
import java.util.Map;
27+
28+
public class SplitHttpClientKerberosImpl implements SplitHttpClient {
29+
30+
private static final Logger _log = LoggerFactory.getLogger(SplitHttpClientKerberosImpl.class);
31+
private static final String HEADER_CACHE_CONTROL_NAME = "Cache-Control";
32+
private static final String HEADER_CACHE_CONTROL_VALUE = "no-cache";
33+
private static final String HEADER_API_KEY = "Authorization";
34+
private static final String HEADER_CLIENT_KEY = "SplitSDKClientKey";
35+
private static final String HEADER_CLIENT_MACHINE_NAME = "SplitSDKMachineName";
36+
private static final String HEADER_CLIENT_MACHINE_IP = "SplitSDKMachineIP";
37+
private static final String HEADER_CLIENT_VERSION = "SplitSDKVersion";
38+
39+
private final RequestDecorator _requestDecorator;
40+
private final String _apikey;
41+
private final SDKMetadata _metadata;
42+
43+
public static SplitHttpClientKerberosImpl create(RequestDecorator requestDecorator,
44+
String apikey,
45+
SDKMetadata metadata) {
46+
return new SplitHttpClientKerberosImpl(requestDecorator, apikey, metadata);
47+
}
48+
49+
SplitHttpClientKerberosImpl(RequestDecorator requestDecorator,
50+
String apikey,
51+
SDKMetadata metadata) {
52+
_requestDecorator = requestDecorator;
53+
_apikey = apikey;
54+
_metadata = metadata;
55+
}
56+
57+
public synchronized SplitHttpResponse get(URI uri, FetchOptions options, Map<String, List<String>> additionalHeaders) {
58+
HttpURLConnection getHttpURLConnection = null;
59+
try {
60+
getHttpURLConnection = (HttpURLConnection) uri.toURL().openConnection();
61+
return doGet(getHttpURLConnection, options, additionalHeaders);
62+
} catch (Exception e) {
63+
throw new IllegalStateException(String.format("Problem in http get operation: %s", e), e);
64+
} finally {
65+
try {
66+
if (getHttpURLConnection != null) {
67+
getHttpURLConnection.disconnect();
68+
}
69+
} catch (Exception e) {
70+
_log.error(String.format("Could not close HTTP URL Connection: %s", e), e);
71+
}
72+
}
73+
}
74+
public SplitHttpResponse doGet(HttpURLConnection getHttpURLConnection, FetchOptions options, Map<String, List<String>> additionalHeaders) {
75+
try {
76+
getHttpURLConnection.setRequestMethod("GET");
77+
setBasicHeaders(getHttpURLConnection);
78+
setAdditionalAndDecoratedHeaders(getHttpURLConnection, additionalHeaders);
79+
80+
if (options.cacheControlHeadersEnabled()) {
81+
getHttpURLConnection.setRequestProperty(HEADER_CACHE_CONTROL_NAME, HEADER_CACHE_CONTROL_VALUE);
82+
}
83+
84+
_log.debug(String.format("Request Headers: %s", getHttpURLConnection.getRequestProperties()));
85+
86+
int responseCode = getHttpURLConnection.getResponseCode();
87+
88+
if (_log.isDebugEnabled()) {
89+
_log.debug(String.format("[%s] %s. Status code: %s",
90+
getHttpURLConnection.getRequestMethod(),
91+
getHttpURLConnection.getURL().toString(),
92+
responseCode));
93+
}
94+
95+
String statusMessage = "";
96+
if (responseCode < HttpURLConnection.HTTP_OK || responseCode >= HttpURLConnection.HTTP_MULT_CHOICE) {
97+
_log.warn(String.format("Response status was: %s. Reason: %s", responseCode,
98+
getHttpURLConnection.getResponseMessage()));
99+
statusMessage = getHttpURLConnection.getResponseMessage();
100+
}
101+
102+
InputStreamReader inputStreamReader = new InputStreamReader(getHttpURLConnection.getInputStream());
103+
BufferedReader br = new BufferedReader(inputStreamReader);
104+
String strCurrentLine;
105+
StringBuilder bld = new StringBuilder();
106+
while ((strCurrentLine = br.readLine()) != null) {
107+
bld.append(strCurrentLine);
108+
}
109+
String responseBody = bld.toString();
110+
inputStreamReader.close();
111+
return new SplitHttpResponse(responseCode,
112+
statusMessage,
113+
responseBody,
114+
getResponseHeaders(getHttpURLConnection));
115+
} catch (Exception e) {
116+
throw new IllegalStateException(String.format("Problem in http get operation: %s", e), e);
117+
}
118+
}
119+
120+
public synchronized SplitHttpResponse post(URI uri, HttpEntity entity, Map<String, List<String>> additionalHeaders) throws IOException {
121+
HttpURLConnection postHttpURLConnection = null;
122+
try {
123+
postHttpURLConnection = (HttpURLConnection) uri.toURL().openConnection();
124+
return doPost(postHttpURLConnection, entity, additionalHeaders);
125+
} catch (Exception e) {
126+
throw new IllegalStateException(String.format("Problem in http post operation: %s", e), e);
127+
} finally {
128+
try {
129+
if (postHttpURLConnection != null) {
130+
postHttpURLConnection.disconnect();
131+
}
132+
} catch (Exception e) {
133+
_log.error(String.format("Could not close URL Connection: %s", e), e);
134+
}
135+
}
136+
}
137+
138+
public SplitHttpResponse doPost(HttpURLConnection postHttpURLConnection,
139+
HttpEntity entity,
140+
Map<String, List<String>> additionalHeaders) {
141+
try {
142+
postHttpURLConnection.setRequestMethod("POST");
143+
setBasicHeaders(postHttpURLConnection);
144+
setAdditionalAndDecoratedHeaders(postHttpURLConnection, additionalHeaders);
145+
146+
postHttpURLConnection.setRequestProperty("Accept-Encoding", "gzip");
147+
postHttpURLConnection.setRequestProperty("Content-Type", "application/json");
148+
_log.debug(String.format("Request Headers: %s", postHttpURLConnection.getRequestProperties()));
149+
150+
postHttpURLConnection.setDoOutput(true);
151+
String postBody = EntityUtils.toString(entity);
152+
OutputStream os = postHttpURLConnection.getOutputStream();
153+
os.write(postBody.getBytes(StandardCharsets.UTF_8));
154+
os.flush();
155+
os.close();
156+
_log.debug(String.format("Posting: %s", postBody));
157+
158+
int responseCode = postHttpURLConnection.getResponseCode();
159+
String statusMessage = "";
160+
if (responseCode < HttpURLConnection.HTTP_OK || responseCode >= HttpURLConnection.HTTP_MULT_CHOICE) {
161+
statusMessage = postHttpURLConnection.getResponseMessage();
162+
_log.warn(String.format("Response status was: %s. Reason: %s", responseCode,
163+
statusMessage));
164+
}
165+
return new SplitHttpResponse(responseCode, statusMessage, "", getResponseHeaders(postHttpURLConnection));
166+
} catch (Exception e) {
167+
throw new IllegalStateException(String.format("Problem in http post operation: %s", e), e);
168+
}
169+
}
170+
171+
private void setBasicHeaders(HttpURLConnection urlConnection) {
172+
urlConnection.setRequestProperty(HEADER_API_KEY, "Bearer " + _apikey);
173+
urlConnection.setRequestProperty(HEADER_CLIENT_VERSION, _metadata.getSdkVersion());
174+
urlConnection.setRequestProperty(HEADER_CLIENT_MACHINE_IP, _metadata.getMachineIp());
175+
urlConnection.setRequestProperty(HEADER_CLIENT_MACHINE_NAME, _metadata.getMachineName());
176+
urlConnection.setRequestProperty(HEADER_CLIENT_KEY, _apikey.length() > 4
177+
? _apikey.substring(_apikey.length() - 4)
178+
: _apikey);
179+
}
180+
181+
private void setAdditionalAndDecoratedHeaders(HttpURLConnection urlConnection, Map<String, List<String>> additionalHeaders) {
182+
if (additionalHeaders != null) {
183+
for (Map.Entry<String, List<String>> entry : additionalHeaders.entrySet()) {
184+
for (String value : entry.getValue()) {
185+
urlConnection.setRequestProperty(entry.getKey(), value);
186+
}
187+
}
188+
}
189+
HttpRequest request = new HttpGet("");
190+
_requestDecorator.decorateHeaders(request);
191+
for (Header header : request.getHeaders()) {
192+
urlConnection.setRequestProperty(header.getName(), header.getValue());
193+
}
194+
}
195+
196+
private Header[] getResponseHeaders(HttpURLConnection urlConnection) {
197+
List<BasicHeader> responseHeaders = new ArrayList<>();
198+
Map<String, List<String>> map = urlConnection.getHeaderFields();
199+
for (Map.Entry<String, List<String>> entry : map.entrySet()) {
200+
if (entry.getKey() != null) {
201+
BasicHeader responseHeader = new BasicHeader(entry.getKey(), entry.getValue());
202+
responseHeaders.add(responseHeader);
203+
}
204+
}
205+
return responseHeaders.toArray(new Header[0]);
206+
}
207+
@Override
208+
public void close() throws IOException {
209+
// Added for compatibility with HttpSplitClient, no action needed as URLConnection objects are closed.
210+
}
211+
}

client/src/test/java/io/split/client/SplitClientConfigTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.split.client.impressions.ImpressionsManager;
77
import io.split.client.dtos.RequestContext;
88
import io.split.integrations.IntegrationsConfig;
9+
import io.split.service.HttpAuthScheme;
910
import org.junit.Assert;
1011
import org.junit.Test;
1112
import org.mockito.Mockito;
@@ -254,4 +255,16 @@ public Map<String, List<String>> getHeaderOverrides(RequestContext context) {
254255
Assert.assertNull(config2.customHeaderDecorator());
255256

256257
}
258+
259+
@Test
260+
public void checkExpectedAuthScheme() {
261+
SplitClientConfig cfg = SplitClientConfig.builder()
262+
.authScheme(HttpAuthScheme.KERBEROS)
263+
.build();
264+
Assert.assertEquals(HttpAuthScheme.KERBEROS, cfg.authScheme());
265+
266+
cfg = SplitClientConfig.builder()
267+
.build();
268+
Assert.assertEquals(null, cfg.authScheme());
269+
}
257270
}

client/src/test/java/io/split/client/SplitFactoryImplTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
import io.split.client.impressions.ImpressionsManager;
44
import io.split.client.utils.FileTypeEnum;
5+
import io.split.client.utils.SDKMetadata;
56
import io.split.integrations.IntegrationsConfig;
7+
import io.split.service.HttpAuthScheme;
8+
import io.split.service.SplitHttpClientKerberosImpl;
69
import io.split.storages.enums.OperationMode;
710
import io.split.storages.pluggable.domain.UserStorageWrapper;
811
import io.split.telemetry.storage.TelemetryStorage;
@@ -22,6 +25,8 @@
2225
import java.lang.reflect.Modifier;
2326
import java.net.URISyntaxException;
2427

28+
import static io.split.client.SplitClientConfig.splitSdkVersion;
29+
2530
public class SplitFactoryImplTest extends TestCase {
2631
public static final String API_KEY ="29013ionasdasd09u";
2732
public static final String ENDPOINT = "https://sdk.split-stage.io";
@@ -344,4 +349,19 @@ public void testLocalhosJsonInputStreamNullAndFileTypeNull() throws URISyntaxExc
344349
Object splitChangeFetcher = method.invoke(splitFactory, splitClientConfig);
345350
Assert.assertTrue(splitChangeFetcher instanceof LegacyLocalhostSplitChangeFetcher);
346351
}
352+
353+
@Test
354+
public void testFactoryKerberosInstance() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
355+
SplitClientConfig splitClientConfig = SplitClientConfig.builder()
356+
.setBlockUntilReadyTimeout(10000)
357+
.authScheme(HttpAuthScheme.KERBEROS)
358+
.build();
359+
SplitFactoryImpl splitFactory = new SplitFactoryImpl("asdf", splitClientConfig);
360+
361+
Method method = SplitFactoryImpl.class.getDeclaredMethod("buildSplitHttpClient", String.class,
362+
SplitClientConfig.class, SDKMetadata.class, RequestDecorator.class);
363+
method.setAccessible(true);
364+
Object SplitHttpClient = method.invoke(splitFactory, "asdf", splitClientConfig, new SDKMetadata(splitSdkVersion, "", ""), new RequestDecorator(null));
365+
Assert.assertTrue(SplitHttpClient instanceof SplitHttpClientKerberosImpl);
366+
}
347367
}

0 commit comments

Comments
 (0)