Skip to content

Commit 0d3776a

Browse files
authored
feat: add support for authenticated datafile access (#378)
1 parent 3eba4fc commit 0d3776a

File tree

4 files changed

+125
-11
lines changed

4 files changed

+125
-11
lines changed

core-httpclient-impl/src/main/java/com/optimizely/ab/OptimizelyFactory.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,29 @@ public static Optimizely newDefaultInstance(String sdkKey) {
179179
* @param fallback Fallback datafile string used by the ProjectConfigManager to be immediately available.
180180
*/
181181
public static Optimizely newDefaultInstance(String sdkKey, String fallback) {
182+
String datafileAccessToken = PropertyUtils.get(HttpProjectConfigManager.CONFIG_DATAFILE_AUTH_TOKEN);
183+
return newDefaultInstance(sdkKey, fallback, datafileAccessToken);
184+
}
185+
186+
/**
187+
* Returns a new Optimizely instance with authenticated datafile support.
188+
*
189+
* @param sdkKey SDK key used to build the ProjectConfigManager.
190+
* @param fallback Fallback datafile string used by the ProjectConfigManager to be immediately available.
191+
* @param datafileAccessToken Token for authenticated datafile access.
192+
*/
193+
public static Optimizely newDefaultInstance(String sdkKey, String fallback, String datafileAccessToken) {
182194
NotificationCenter notificationCenter = new NotificationCenter();
183195

184196
HttpProjectConfigManager.Builder builder = HttpProjectConfigManager.builder()
185197
.withDatafile(fallback)
186198
.withNotificationCenter(notificationCenter)
187199
.withSdkKey(sdkKey);
188200

201+
if (datafileAccessToken != null) {
202+
builder.withDatafileAccessToken(datafileAccessToken);
203+
}
204+
189205
return newDefaultInstance(builder.build(), notificationCenter);
190206
}
191207

core-httpclient-impl/src/main/java/com/optimizely/ab/config/HttpProjectConfigManager.java

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.optimizely.ab.HttpClientUtils;
2020
import com.optimizely.ab.OptimizelyHttpClient;
21+
import com.optimizely.ab.annotations.VisibleForTesting;
2122
import com.optimizely.ab.config.parser.ConfigParseException;
2223
import com.optimizely.ab.internal.PropertyUtils;
2324
import com.optimizely.ab.notification.NotificationCenter;
@@ -44,6 +45,7 @@ public class HttpProjectConfigManager extends PollingProjectConfigManager {
4445
public static final String CONFIG_BLOCKING_DURATION = "http.project.config.manager.blocking.duration";
4546
public static final String CONFIG_BLOCKING_UNIT = "http.project.config.manager.blocking.unit";
4647
public static final String CONFIG_SDK_KEY = "http.project.config.manager.sdk.key";
48+
public static final String CONFIG_DATAFILE_AUTH_TOKEN = "http.project.config.manager.datafile.auth.token";
4749

4850
public static final long DEFAULT_POLLING_DURATION = 5;
4951
public static final TimeUnit DEFAULT_POLLING_UNIT = TimeUnit.MINUTES;
@@ -54,12 +56,21 @@ public class HttpProjectConfigManager extends PollingProjectConfigManager {
5456

5557
private final OptimizelyHttpClient httpClient;
5658
private final URI uri;
59+
private final String datafileAccessToken;
5760
private String datafileLastModified;
5861

59-
private HttpProjectConfigManager(long period, TimeUnit timeUnit, OptimizelyHttpClient httpClient, String url, long blockingTimeoutPeriod, TimeUnit blockingTimeoutUnit, NotificationCenter notificationCenter) {
62+
private HttpProjectConfigManager(long period,
63+
TimeUnit timeUnit,
64+
OptimizelyHttpClient httpClient,
65+
String url,
66+
String datafileAccessToken,
67+
long blockingTimeoutPeriod,
68+
TimeUnit blockingTimeoutUnit,
69+
NotificationCenter notificationCenter) {
6070
super(period, timeUnit, blockingTimeoutPeriod, blockingTimeoutUnit, notificationCenter);
6171
this.httpClient = httpClient;
6272
this.uri = URI.create(url);
73+
this.datafileAccessToken = datafileAccessToken;
6374
}
6475

6576
public URI getUri() {
@@ -104,11 +115,7 @@ static ProjectConfig parseProjectConfig(String datafile) throws ConfigParseExcep
104115

105116
@Override
106117
protected ProjectConfig poll() {
107-
HttpGet httpGet = new HttpGet(uri);
108-
109-
if (datafileLastModified != null) {
110-
httpGet.setHeader(HttpHeaders.IF_MODIFIED_SINCE, datafileLastModified);
111-
}
118+
HttpGet httpGet = createHttpRequest();
112119

113120
logger.debug("Fetching datafile from: {}", httpGet.getURI());
114121
try {
@@ -125,14 +132,31 @@ protected ProjectConfig poll() {
125132
return null;
126133
}
127134

135+
@VisibleForTesting
136+
HttpGet createHttpRequest() {
137+
HttpGet httpGet = new HttpGet(uri);
138+
139+
if (datafileAccessToken != null) {
140+
httpGet.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + datafileAccessToken);
141+
}
142+
143+
if (datafileLastModified != null) {
144+
httpGet.setHeader(HttpHeaders.IF_MODIFIED_SINCE, datafileLastModified);
145+
}
146+
147+
return httpGet;
148+
}
149+
128150
public static Builder builder() {
129151
return new Builder();
130152
}
131153

132154
public static class Builder {
133155
private String datafile;
134156
private String url;
157+
private String datafileAccessToken = null;
135158
private String format = "https://cdn.optimizely.com/datafiles/%s.json";
159+
private String authFormat = "https://config.optimizely.com/datafiles/auth/%s.json";
136160
private OptimizelyHttpClient httpClient;
137161
private NotificationCenter notificationCenter;
138162

@@ -153,6 +177,11 @@ public Builder withSdkKey(String sdkKey) {
153177
return this;
154178
}
155179

180+
public Builder withDatafileAccessToken(String token) {
181+
this.datafileAccessToken = token;
182+
return this;
183+
}
184+
156185
public Builder withUrl(String url) {
157186
this.url = url;
158187
return this;
@@ -261,14 +290,26 @@ public HttpProjectConfigManager build(boolean defer) {
261290
throw new NullPointerException("sdkKey cannot be null");
262291
}
263292

264-
url = String.format(format, sdkKey);
293+
if (datafileAccessToken == null) {
294+
url = String.format(format, sdkKey);
295+
} else {
296+
url = String.format(authFormat, sdkKey);
297+
}
265298
}
266299

267300
if (notificationCenter == null) {
268301
notificationCenter = new NotificationCenter();
269302
}
270303

271-
HttpProjectConfigManager httpProjectManager = new HttpProjectConfigManager(period, timeUnit, httpClient, url, blockingTimeoutPeriod, blockingTimeoutUnit, notificationCenter);
304+
HttpProjectConfigManager httpProjectManager = new HttpProjectConfigManager(
305+
period,
306+
timeUnit,
307+
httpClient,
308+
url,
309+
datafileAccessToken,
310+
blockingTimeoutPeriod,
311+
blockingTimeoutUnit,
312+
notificationCenter);
272313

273314
if (datafile != null) {
274315
try {

core-httpclient-impl/src/test/java/com/optimizely/ab/OptimizelyFactoryTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,13 @@ public void newDefaultInstanceWithFallback() throws Exception {
191191
assertTrue(optimizely.isValid());
192192
}
193193

194+
@Test
195+
public void newDefaultInstanceWithDatafileAccessToken() throws Exception {
196+
String datafileString = Resources.toString(Resources.getResource("valid-project-config-v4.json"), Charsets.UTF_8);
197+
optimizely = OptimizelyFactory.newDefaultInstance("sdk-key", datafileString, "auth-token");
198+
assertTrue(optimizely.isValid());
199+
}
200+
194201
@Test
195202
public void newDefaultInstanceWithProjectConfig() throws Exception {
196203
optimizely = OptimizelyFactory.newDefaultInstance(() -> null);

core-httpclient-impl/src/test/java/com/optimizely/ab/config/HttpProjectConfigManagerTest.java

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@
4343
import static java.util.concurrent.TimeUnit.SECONDS;
4444
import static org.junit.Assert.*;
4545
import static org.mockito.Matchers.any;
46-
import static org.mockito.Mockito.mock;
47-
import static org.mockito.Mockito.reset;
48-
import static org.mockito.Mockito.when;
46+
import static org.mockito.Mockito.*;
4947

5048
@RunWith(MockitoJUnitRunner.class)
5149
public class HttpProjectConfigManagerTest {
@@ -125,6 +123,58 @@ public void testHttpGetByCustomUrl() throws Exception {
125123
assertEquals(new URI(expected), actual);
126124
}
127125

126+
@Test
127+
public void testHttpGetBySdkKeyForAuthDatafile() throws Exception {
128+
projectConfigManager = builder()
129+
.withOptimizelyHttpClient(mockHttpClient)
130+
.withSdkKey("sdk-key")
131+
.withDatafileAccessToken("auth-token")
132+
.build();
133+
134+
URI actual = projectConfigManager.getUri();
135+
assertEquals(new URI("https://config.optimizely.com/datafiles/auth/sdk-key.json"), actual);
136+
}
137+
138+
@Test
139+
public void testHttpGetByCustomUrlForAuthDatafile() throws Exception {
140+
String expected = "https://custom.optimizely.com/custom-location.json";
141+
142+
projectConfigManager = builder()
143+
.withOptimizelyHttpClient(mockHttpClient)
144+
.withUrl(expected)
145+
.withSdkKey("sdk-key")
146+
.withDatafileAccessToken("auth-token")
147+
.build();
148+
149+
URI actual = projectConfigManager.getUri();
150+
assertEquals(new URI(expected), actual);
151+
}
152+
153+
@Test
154+
public void testCreateHttpRequest() throws Exception {
155+
projectConfigManager = builder()
156+
.withOptimizelyHttpClient(mockHttpClient)
157+
.withSdkKey("sdk-key")
158+
.build();
159+
160+
HttpGet request = projectConfigManager.createHttpRequest();
161+
assertEquals(request.getURI().toString(), "https://cdn.optimizely.com/datafiles/sdk-key.json");
162+
assertEquals(request.getHeaders("Authorization").length, 0);
163+
}
164+
165+
@Test
166+
public void testCreateHttpRequestForAuthDatafile() throws Exception {
167+
projectConfigManager = builder()
168+
.withOptimizelyHttpClient(mockHttpClient)
169+
.withSdkKey("sdk-key")
170+
.withDatafileAccessToken("auth-token")
171+
.build();
172+
173+
HttpGet request = projectConfigManager.createHttpRequest();
174+
assertEquals(request.getURI().toString(), "https://config.optimizely.com/datafiles/auth/sdk-key.json");
175+
assertEquals(request.getHeaders("Authorization")[0].getValue(), "Bearer auth-token");
176+
}
177+
128178
@Test
129179
public void testPoll() throws Exception {
130180
projectConfigManager = builder()

0 commit comments

Comments
 (0)