diff --git a/core-httpclient-impl/src/main/java/com/optimizely/ab/OptimizelyFactory.java b/core-httpclient-impl/src/main/java/com/optimizely/ab/OptimizelyFactory.java index 93c08638b..989e578f0 100644 --- a/core-httpclient-impl/src/main/java/com/optimizely/ab/OptimizelyFactory.java +++ b/core-httpclient-impl/src/main/java/com/optimizely/ab/OptimizelyFactory.java @@ -179,6 +179,18 @@ public static Optimizely newDefaultInstance(String sdkKey) { * @param fallback Fallback datafile string used by the ProjectConfigManager to be immediately available. */ public static Optimizely newDefaultInstance(String sdkKey, String fallback) { + String datafileAccessToken = PropertyUtils.get(HttpProjectConfigManager.CONFIG_DATAFILE_AUTH_TOKEN); + return newDefaultInstance(sdkKey, fallback, datafileAccessToken); + } + + /** + * Returns a new Optimizely instance with authenticated datafile support. + * + * @param sdkKey SDK key used to build the ProjectConfigManager. + * @param fallback Fallback datafile string used by the ProjectConfigManager to be immediately available. + * @param datafileAccessToken Token for authenticated datafile access. + */ + public static Optimizely newDefaultInstance(String sdkKey, String fallback, String datafileAccessToken) { NotificationCenter notificationCenter = new NotificationCenter(); HttpProjectConfigManager.Builder builder = HttpProjectConfigManager.builder() @@ -186,6 +198,10 @@ public static Optimizely newDefaultInstance(String sdkKey, String fallback) { .withNotificationCenter(notificationCenter) .withSdkKey(sdkKey); + if (datafileAccessToken != null) { + builder.withDatafileAccessToken(datafileAccessToken); + } + return newDefaultInstance(builder.build(), notificationCenter); } diff --git a/core-httpclient-impl/src/main/java/com/optimizely/ab/config/HttpProjectConfigManager.java b/core-httpclient-impl/src/main/java/com/optimizely/ab/config/HttpProjectConfigManager.java index f2f8a61be..afe7db451 100644 --- a/core-httpclient-impl/src/main/java/com/optimizely/ab/config/HttpProjectConfigManager.java +++ b/core-httpclient-impl/src/main/java/com/optimizely/ab/config/HttpProjectConfigManager.java @@ -18,6 +18,7 @@ import com.optimizely.ab.HttpClientUtils; import com.optimizely.ab.OptimizelyHttpClient; +import com.optimizely.ab.annotations.VisibleForTesting; import com.optimizely.ab.config.parser.ConfigParseException; import com.optimizely.ab.internal.PropertyUtils; import com.optimizely.ab.notification.NotificationCenter; @@ -44,6 +45,7 @@ public class HttpProjectConfigManager extends PollingProjectConfigManager { public static final String CONFIG_BLOCKING_DURATION = "http.project.config.manager.blocking.duration"; public static final String CONFIG_BLOCKING_UNIT = "http.project.config.manager.blocking.unit"; public static final String CONFIG_SDK_KEY = "http.project.config.manager.sdk.key"; + public static final String CONFIG_DATAFILE_AUTH_TOKEN = "http.project.config.manager.datafile.auth.token"; public static final long DEFAULT_POLLING_DURATION = 5; public static final TimeUnit DEFAULT_POLLING_UNIT = TimeUnit.MINUTES; @@ -54,12 +56,21 @@ public class HttpProjectConfigManager extends PollingProjectConfigManager { private final OptimizelyHttpClient httpClient; private final URI uri; + private final String datafileAccessToken; private String datafileLastModified; - private HttpProjectConfigManager(long period, TimeUnit timeUnit, OptimizelyHttpClient httpClient, String url, long blockingTimeoutPeriod, TimeUnit blockingTimeoutUnit, NotificationCenter notificationCenter) { + private HttpProjectConfigManager(long period, + TimeUnit timeUnit, + OptimizelyHttpClient httpClient, + String url, + String datafileAccessToken, + long blockingTimeoutPeriod, + TimeUnit blockingTimeoutUnit, + NotificationCenter notificationCenter) { super(period, timeUnit, blockingTimeoutPeriod, blockingTimeoutUnit, notificationCenter); this.httpClient = httpClient; this.uri = URI.create(url); + this.datafileAccessToken = datafileAccessToken; } public URI getUri() { @@ -104,11 +115,7 @@ static ProjectConfig parseProjectConfig(String datafile) throws ConfigParseExcep @Override protected ProjectConfig poll() { - HttpGet httpGet = new HttpGet(uri); - - if (datafileLastModified != null) { - httpGet.setHeader(HttpHeaders.IF_MODIFIED_SINCE, datafileLastModified); - } + HttpGet httpGet = createHttpRequest(); logger.debug("Fetching datafile from: {}", httpGet.getURI()); try { @@ -125,6 +132,21 @@ protected ProjectConfig poll() { return null; } + @VisibleForTesting + HttpGet createHttpRequest() { + HttpGet httpGet = new HttpGet(uri); + + if (datafileAccessToken != null) { + httpGet.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + datafileAccessToken); + } + + if (datafileLastModified != null) { + httpGet.setHeader(HttpHeaders.IF_MODIFIED_SINCE, datafileLastModified); + } + + return httpGet; + } + public static Builder builder() { return new Builder(); } @@ -132,7 +154,9 @@ public static Builder builder() { public static class Builder { private String datafile; private String url; + private String datafileAccessToken = null; private String format = "https://cdn.optimizely.com/datafiles/%s.json"; + private String authFormat = "https://config.optimizely.com/datafiles/auth/%s.json"; private OptimizelyHttpClient httpClient; private NotificationCenter notificationCenter; @@ -153,6 +177,11 @@ public Builder withSdkKey(String sdkKey) { return this; } + public Builder withDatafileAccessToken(String token) { + this.datafileAccessToken = token; + return this; + } + public Builder withUrl(String url) { this.url = url; return this; @@ -261,14 +290,26 @@ public HttpProjectConfigManager build(boolean defer) { throw new NullPointerException("sdkKey cannot be null"); } - url = String.format(format, sdkKey); + if (datafileAccessToken == null) { + url = String.format(format, sdkKey); + } else { + url = String.format(authFormat, sdkKey); + } } if (notificationCenter == null) { notificationCenter = new NotificationCenter(); } - HttpProjectConfigManager httpProjectManager = new HttpProjectConfigManager(period, timeUnit, httpClient, url, blockingTimeoutPeriod, blockingTimeoutUnit, notificationCenter); + HttpProjectConfigManager httpProjectManager = new HttpProjectConfigManager( + period, + timeUnit, + httpClient, + url, + datafileAccessToken, + blockingTimeoutPeriod, + blockingTimeoutUnit, + notificationCenter); if (datafile != null) { try { diff --git a/core-httpclient-impl/src/test/java/com/optimizely/ab/OptimizelyFactoryTest.java b/core-httpclient-impl/src/test/java/com/optimizely/ab/OptimizelyFactoryTest.java index 9dfe0658e..8b595a019 100644 --- a/core-httpclient-impl/src/test/java/com/optimizely/ab/OptimizelyFactoryTest.java +++ b/core-httpclient-impl/src/test/java/com/optimizely/ab/OptimizelyFactoryTest.java @@ -191,6 +191,13 @@ public void newDefaultInstanceWithFallback() throws Exception { assertTrue(optimizely.isValid()); } + @Test + public void newDefaultInstanceWithDatafileAccessToken() throws Exception { + String datafileString = Resources.toString(Resources.getResource("valid-project-config-v4.json"), Charsets.UTF_8); + optimizely = OptimizelyFactory.newDefaultInstance("sdk-key", datafileString, "auth-token"); + assertTrue(optimizely.isValid()); + } + @Test public void newDefaultInstanceWithProjectConfig() throws Exception { optimizely = OptimizelyFactory.newDefaultInstance(() -> null); diff --git a/core-httpclient-impl/src/test/java/com/optimizely/ab/config/HttpProjectConfigManagerTest.java b/core-httpclient-impl/src/test/java/com/optimizely/ab/config/HttpProjectConfigManagerTest.java index aa5555de4..43c0d3c31 100644 --- a/core-httpclient-impl/src/test/java/com/optimizely/ab/config/HttpProjectConfigManagerTest.java +++ b/core-httpclient-impl/src/test/java/com/optimizely/ab/config/HttpProjectConfigManagerTest.java @@ -43,9 +43,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.*; import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class HttpProjectConfigManagerTest { @@ -125,6 +123,58 @@ public void testHttpGetByCustomUrl() throws Exception { assertEquals(new URI(expected), actual); } + @Test + public void testHttpGetBySdkKeyForAuthDatafile() throws Exception { + projectConfigManager = builder() + .withOptimizelyHttpClient(mockHttpClient) + .withSdkKey("sdk-key") + .withDatafileAccessToken("auth-token") + .build(); + + URI actual = projectConfigManager.getUri(); + assertEquals(new URI("https://config.optimizely.com/datafiles/auth/sdk-key.json"), actual); + } + + @Test + public void testHttpGetByCustomUrlForAuthDatafile() throws Exception { + String expected = "https://custom.optimizely.com/custom-location.json"; + + projectConfigManager = builder() + .withOptimizelyHttpClient(mockHttpClient) + .withUrl(expected) + .withSdkKey("sdk-key") + .withDatafileAccessToken("auth-token") + .build(); + + URI actual = projectConfigManager.getUri(); + assertEquals(new URI(expected), actual); + } + + @Test + public void testCreateHttpRequest() throws Exception { + projectConfigManager = builder() + .withOptimizelyHttpClient(mockHttpClient) + .withSdkKey("sdk-key") + .build(); + + HttpGet request = projectConfigManager.createHttpRequest(); + assertEquals(request.getURI().toString(), "https://cdn.optimizely.com/datafiles/sdk-key.json"); + assertEquals(request.getHeaders("Authorization").length, 0); + } + + @Test + public void testCreateHttpRequestForAuthDatafile() throws Exception { + projectConfigManager = builder() + .withOptimizelyHttpClient(mockHttpClient) + .withSdkKey("sdk-key") + .withDatafileAccessToken("auth-token") + .build(); + + HttpGet request = projectConfigManager.createHttpRequest(); + assertEquals(request.getURI().toString(), "https://config.optimizely.com/datafiles/auth/sdk-key.json"); + assertEquals(request.getHeaders("Authorization")[0].getValue(), "Bearer auth-token"); + } + @Test public void testPoll() throws Exception { projectConfigManager = builder()