Skip to content

feat: Added support for Authenticated Datafiles #255

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 6, 2020
2 changes: 1 addition & 1 deletion .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Lint/HandleExceptions:
# Offense count: 8
# Configuration parameters: CountKeywordArgs.
Metrics/ParameterLists:
Max: 12
Max: 13

# Offense count: 2
Naming/AccessorMethodName:
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Optimizely Ruby SDK Changelog

## [Unreleased]
- Added support for authenticated datafiles. `HTTPProjectConfigManager` now accepts `datafile_access_token` to be able to fetch authenticated datafiles.

## 3.4.0
January 23rd, 2020

Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ The `HTTPConfigManager` asynchronously polls for datafiles from a specified URL
logger: nil,
error_handler: nil,
skip_json_validation: false,
notification_center: notification_center
notification_center: notification_center,
datafile_access_token: nil
)
~~~~~~
**Note:** You must provide either the `sdk_key` or URL. If you provide both, the URL takes precedence.
Expand All @@ -110,6 +111,9 @@ Boolean flag used to start the `AsyncScheduler` for datafile polling if set to `
**blocking_timeout**
The blocking timeout period is used to specify a maximum time to wait for initial bootstrapping. Valid blocking timeout period is between 1 and 2592000 seconds. Default is 15 seconds.

**datafile_access_token**
An access token sent in an authorization header with the request to fetch private datafiles.

You may also provide your own logger, error handler, or notification center.


Expand Down
8 changes: 6 additions & 2 deletions lib/optimizely/config_manager/http_project_config_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class HTTPProjectConfigManager < ProjectConfigManager
# error_handler - Provides a handle_error method to handle exceptions.
# skip_json_validation - Optional boolean param which allows skipping JSON schema
# validation upon object invocation. By default JSON schema validation will be performed.
# datafile_access_token - access token used to fetch private datafiles
def initialize(
sdk_key: nil,
url: nil,
Expand All @@ -63,10 +64,12 @@ def initialize(
logger: nil,
error_handler: nil,
skip_json_validation: false,
notification_center: nil
notification_center: nil,
datafile_access_token: nil
)
@logger = logger || NoOpLogger.new
@error_handler = error_handler || NoOpErrorHandler.new
@access_token = datafile_access_token
@datafile_url = get_datafile_url(sdk_key, url, url_template)
@polling_interval = nil
polling_interval(polling_interval)
Expand Down Expand Up @@ -153,6 +156,7 @@ def request_config
headers = {}
headers['Content-Type'] = 'application/json'
headers['If-Modified-Since'] = @last_modified if @last_modified
headers['Authorization'] = "Bearer #{@access_token}" unless @access_token.nil?

response = Helpers::HttpUtils.make_request(
@datafile_url, :get, nil, headers, Helpers::Constants::CONFIG_MANAGER['REQUEST_TIMEOUT']
Expand Down Expand Up @@ -288,7 +292,7 @@ def get_datafile_url(sdk_key, url, url_template)
end

unless url
url_template ||= Helpers::Constants::CONFIG_MANAGER['DATAFILE_URL_TEMPLATE']
url_template ||= @access_token.nil? ? Helpers::Constants::CONFIG_MANAGER['DATAFILE_URL_TEMPLATE'] : Helpers::Constants::CONFIG_MANAGER['AUTHENTICATED_DATAFILE_URL_TEMPLATE']
begin
return (url_template % sdk_key)
rescue
Expand Down
1 change: 1 addition & 0 deletions lib/optimizely/helpers/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ module Constants

CONFIG_MANAGER = {
'DATAFILE_URL_TEMPLATE' => 'https://cdn.optimizely.com/datafiles/%s.json',
'AUTHENTICATED_DATAFILE_URL_TEMPLATE' => 'https://config.optimizely.com/datafiles/auth/%s.json',
# Default time in seconds to block the 'config' method call until 'config' instance has been initialized.
'DEFAULT_BLOCKING_TIMEOUT' => 15,
# Default config update interval of 5 minutes
Expand Down
49 changes: 43 additions & 6 deletions spec/config_manager/http_project_config_manager_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,7 @@
# limitations under the License.
#
require 'spec_helper'
require 'optimizely/config_manager/http_project_config_manager'
require 'optimizely/error_handler'
require 'optimizely/exceptions'
require 'optimizely/helpers/constants'
require 'optimizely/helpers/validator'
require 'optimizely/logger'

describe Optimizely::HTTPProjectConfigManager do
let(:config_body_JSON) { OptimizelySpec::VALID_CONFIG_BODY_JSON }
let(:error_handler) { Optimizely::RaiseErrorHandler.new }
Expand Down Expand Up @@ -468,4 +463,46 @@
expect(@http_project_config_manager.optimizely_config['revision']).to eq('81')
end
end

describe 'datafile authentication' do
it 'should add authorization header when auth token is provided' do
allow(Optimizely::Helpers::HttpUtils).to receive(:make_request)
@http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
sdk_key: 'valid_sdk_key',
datafile_access_token: 'the-token'
)
sleep 0.1
expect(Optimizely::Helpers::HttpUtils).to have_received(:make_request).with(anything, anything, anything, hash_including('Authorization' => 'Bearer the-token'), anything)
end

it 'should use authenticated datafile url when auth token is provided' do
allow(Optimizely::Helpers::HttpUtils).to receive(:make_request).and_return(VALID_SDK_KEY_CONFIG_JSON)
@http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
sdk_key: 'valid_sdk_key',
datafile_access_token: 'the-token'
)
sleep 0.1
expect(Optimizely::Helpers::HttpUtils).to have_received(:make_request).with('https://config.optimizely.com/datafiles/auth/valid_sdk_key.json', any_args)
end

it 'should use public datafile url when auth token is not provided' do
allow(Optimizely::Helpers::HttpUtils).to receive(:make_request).and_return(VALID_SDK_KEY_CONFIG_JSON)
@http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
sdk_key: 'valid_sdk_key'
)
sleep 0.1
expect(Optimizely::Helpers::HttpUtils).to have_received(:make_request).with('https://cdn.optimizely.com/datafiles/valid_sdk_key.json', any_args)
end

it 'should prefer user provided template url over defaults' do
allow(Optimizely::Helpers::HttpUtils).to receive(:make_request).and_return(VALID_SDK_KEY_CONFIG_JSON)
@http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
sdk_key: 'valid_sdk_key',
datafile_access_token: 'the-token',
url_template: 'http://awesomeurl'
)
sleep 0.1
expect(Optimizely::Helpers::HttpUtils).to have_received(:make_request).with('http://awesomeurl', any_args)
end
end
end