diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b4767f90..c49d5397 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -14,7 +14,7 @@ Lint/HandleExceptions: # Offense count: 8 # Configuration parameters: CountKeywordArgs. Metrics/ParameterLists: - Max: 12 + Max: 13 # Offense count: 2 Naming/AccessorMethodName: diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a3cbe64..0d815071 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index be9e7550..7965ea50 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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. diff --git a/lib/optimizely/config_manager/http_project_config_manager.rb b/lib/optimizely/config_manager/http_project_config_manager.rb index e7f993f8..411f5744 100644 --- a/lib/optimizely/config_manager/http_project_config_manager.rb +++ b/lib/optimizely/config_manager/http_project_config_manager.rb @@ -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, @@ -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) @@ -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'] @@ -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 diff --git a/lib/optimizely/helpers/constants.rb b/lib/optimizely/helpers/constants.rb index 1f962c0b..17136571 100644 --- a/lib/optimizely/helpers/constants.rb +++ b/lib/optimizely/helpers/constants.rb @@ -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 diff --git a/spec/config_manager/http_project_config_manager_spec.rb b/spec/config_manager/http_project_config_manager_spec.rb index 321db155..5576a47b 100644 --- a/spec/config_manager/http_project_config_manager_spec.rb +++ b/spec/config_manager/http_project_config_manager_spec.rb @@ -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 } @@ -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