Skip to content

Commit fa8de3f

Browse files
author
Bilal Al
committed
added kerberos auth support
1 parent 50dc950 commit fa8de3f

File tree

6 files changed

+493
-6
lines changed

6 files changed

+493
-6
lines changed

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

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@
1010
import pluggable.CustomStorageWrapper;
1111

1212
import java.io.IOException;
13-
import java.util.HashSet;
14-
import java.util.LinkedHashSet;
15-
import java.util.List;
16-
import java.util.Properties;
13+
import java.util.*;
1714
import java.util.concurrent.ThreadFactory;
1815
import java.io.InputStream;
1916

@@ -91,6 +88,7 @@ public class SplitClientConfig {
9188
private final HashSet<String> _flagSetsFilter;
9289
private final int _invalidSets;
9390
private final CustomHeaderDecorator _customHeaderDecorator;
91+
private final String _authScheme;
9492

9593

9694
public static Builder builder() {
@@ -148,7 +146,8 @@ private SplitClientConfig(String endpoint,
148146
ThreadFactory threadFactory,
149147
HashSet<String> flagSetsFilter,
150148
int invalidSets,
151-
CustomHeaderDecorator customHeaderDecorator) {
149+
CustomHeaderDecorator customHeaderDecorator,
150+
String authScheme) {
152151
_endpoint = endpoint;
153152
_eventsEndpoint = eventsEndpoint;
154153
_featuresRefreshRate = pollForFeatureChangesEveryNSeconds;
@@ -201,6 +200,7 @@ private SplitClientConfig(String endpoint,
201200
_flagSetsFilter = flagSetsFilter;
202201
_invalidSets = invalidSets;
203202
_customHeaderDecorator = customHeaderDecorator;
203+
_authScheme = authScheme;
204204

205205
Properties props = new Properties();
206206
try {
@@ -408,6 +408,9 @@ public int getInvalidSets() {
408408
public CustomHeaderDecorator customHeaderDecorator() {
409409
return _customHeaderDecorator;
410410
}
411+
public String authScheme() {
412+
return _authScheme;
413+
}
411414

412415
public static final class Builder {
413416

@@ -466,6 +469,7 @@ public static final class Builder {
466469
private HashSet<String> _flagSetsFilter = new HashSet<>();
467470
private int _invalidSetsCount = 0;
468471
private CustomHeaderDecorator _customHeaderDecorator = null;
472+
private String _authScheme = null;
469473

470474
public Builder() {
471475
}
@@ -960,6 +964,17 @@ public Builder customHeaderDecorator(CustomHeaderDecorator customHeaderDecorator
960964
return this;
961965
}
962966

967+
/**
968+
* Authentication Scheme
969+
*
970+
* @param authScheme
971+
* @return this builder
972+
*/
973+
public Builder authScheme(String authScheme) {
974+
_authScheme = authScheme;
975+
return this;
976+
}
977+
963978
/**
964979
* Thread Factory
965980
*
@@ -1068,6 +1083,13 @@ public SplitClientConfig build() {
10681083
_storageMode = StorageMode.PLUGGABLE;
10691084
}
10701085

1086+
if(_authScheme != null) {
1087+
if (!_authScheme.toLowerCase(Locale.ROOT).equals("kerberos")) {
1088+
throw new IllegalArgumentException("authScheme must be either null or `kerberos`.");
1089+
}
1090+
_authScheme = "kerberos";
1091+
}
1092+
10711093
return new SplitClientConfig(
10721094
_endpoint,
10731095
_eventsEndpoint,
@@ -1120,7 +1142,8 @@ public SplitClientConfig build() {
11201142
_threadFactory,
11211143
_flagSetsFilter,
11221144
_invalidSetsCount,
1123-
_customHeaderDecorator);
1145+
_customHeaderDecorator,
1146+
_authScheme);
11241147
}
11251148
}
11261149
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import io.split.integrations.IntegrationsConfig;
6060
import io.split.service.SplitHttpClient;
6161
import io.split.service.SplitHttpClientImpl;
62+
import io.split.service.SplitHttpClientKerberosImpl;
6263
import io.split.storages.SegmentCache;
6364
import io.split.storages.SegmentCacheConsumer;
6465
import io.split.storages.SegmentCacheProducer;
@@ -525,6 +526,13 @@ private static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClient
525526
httpClientbuilder = setupProxy(httpClientbuilder, config);
526527
}
527528

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

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,29 @@ public Map<String, List<String>> getHeaderOverrides(RequestContext context) {
254254
Assert.assertNull(config2.customHeaderDecorator());
255255

256256
}
257+
258+
@Test
259+
public void checkExpectedAuthScheme() {
260+
SplitClientConfig cfg = SplitClientConfig.builder()
261+
.authScheme("kerberos")
262+
.build();
263+
Assert.assertEquals("kerberos", cfg.authScheme());
264+
265+
cfg = SplitClientConfig.builder()
266+
.authScheme("KERberos")
267+
.build();
268+
Assert.assertEquals("kerberos", cfg.authScheme());
269+
270+
cfg = SplitClientConfig.builder()
271+
.build();
272+
Assert.assertEquals(null, cfg.authScheme());
273+
}
274+
275+
@Test(expected = IllegalArgumentException.class)
276+
public void checkUnexpectedAuthScheme() {
277+
SplitClientConfig cfg = SplitClientConfig.builder()
278+
.authScheme("proxy")
279+
.build();
280+
Assert.assertEquals(null, cfg.authScheme());
281+
}
257282
}

0 commit comments

Comments
 (0)