Skip to content

Commit 5fed301

Browse files
feat: added authenticated datafile support (#267)
* feat: added authenticated datafile support
1 parent 16569ae commit 5fed301

File tree

7 files changed

+185
-125
lines changed

7 files changed

+185
-125
lines changed

pkg/client/factory.go

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2019, Optimizely, Inc. and contributors *
2+
* Copyright 2019-2020, Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -33,8 +33,9 @@ import (
3333

3434
// OptimizelyFactory is used to customize and construct an instance of the OptimizelyClient.
3535
type OptimizelyFactory struct {
36-
SDKKey string
37-
Datafile []byte
36+
SDKKey string
37+
Datafile []byte
38+
DatafileAccessToken string
3839

3940
configManager config.ProjectConfigManager
4041
ctx context.Context
@@ -50,10 +51,10 @@ type OptimizelyFactory struct {
5051
type OptionFunc func(*OptimizelyFactory)
5152

5253
// Client instantiates a new OptimizelyClient with the given options.
53-
func (f OptimizelyFactory) Client(clientOptions ...OptionFunc) (*OptimizelyClient, error) {
54+
func (f *OptimizelyFactory) Client(clientOptions ...OptionFunc) (*OptimizelyClient, error) {
5455
// extract options
5556
for _, opt := range clientOptions {
56-
opt(&f)
57+
opt(f)
5758
}
5859

5960
if f.SDKKey == "" && f.Datafile == nil && f.configManager == nil {
@@ -85,6 +86,7 @@ func (f OptimizelyFactory) Client(clientOptions ...OptionFunc) (*OptimizelyClien
8586
appClient.ConfigManager = config.NewPollingProjectConfigManager(
8687
f.SDKKey,
8788
config.WithInitialDatafile(f.Datafile),
89+
config.WithDatafileAccessToken(f.DatafileAccessToken),
8890
)
8991
}
9092

@@ -128,6 +130,13 @@ func (f OptimizelyFactory) Client(clientOptions ...OptionFunc) (*OptimizelyClien
128130
return appClient, nil
129131
}
130132

133+
// WithDatafileAccessToken sets authenticated datafile token
134+
func WithDatafileAccessToken(datafileAccessToken string) OptionFunc {
135+
return func(f *OptimizelyFactory) {
136+
f.DatafileAccessToken = datafileAccessToken
137+
}
138+
}
139+
131140
// WithPollingConfigManager sets polling config manager on a client.
132141
func WithPollingConfigManager(pollingInterval time.Duration, initDataFile []byte) OptionFunc {
133142
return func(f *OptimizelyFactory) {
@@ -136,6 +145,14 @@ func WithPollingConfigManager(pollingInterval time.Duration, initDataFile []byte
136145
}
137146
}
138147

148+
// WithPollingConfigManagerDatafileAccessToken sets polling config manager with auth datafile token on a client
149+
func WithPollingConfigManagerDatafileAccessToken(pollingInterval time.Duration, initDataFile []byte, datafileAccessToken string) OptionFunc {
150+
return func(f *OptimizelyFactory) {
151+
f.configManager = config.NewPollingProjectConfigManager(f.SDKKey, config.WithInitialDatafile(initDataFile),
152+
config.WithPollingInterval(pollingInterval), config.WithDatafileAccessToken(datafileAccessToken))
153+
}
154+
}
155+
139156
// WithConfigManager sets polling config manager on a client.
140157
func WithConfigManager(configManager config.ProjectConfigManager) OptionFunc {
141158
return func(f *OptimizelyFactory) {
@@ -201,32 +218,18 @@ func WithMetricsRegistry(metricsRegistry metrics.Registry) OptionFunc {
201218
}
202219

203220
// StaticClient returns a client initialized with a static project config.
204-
func (f OptimizelyFactory) StaticClient() (*OptimizelyClient, error) {
205-
var configManager config.ProjectConfigManager
206-
207-
if f.SDKKey != "" {
208-
staticConfigManager, err := config.NewStaticProjectConfigManagerFromURL(f.SDKKey)
209-
210-
if err != nil {
211-
return nil, err
212-
}
221+
func (f *OptimizelyFactory) StaticClient() (optlyClient *OptimizelyClient, err error) {
213222

214-
configManager = staticConfigManager
215-
216-
} else if f.Datafile != nil {
217-
staticConfigManager, err := config.NewStaticProjectConfigManagerFromPayload(f.Datafile, logging.GetLogger(f.SDKKey, "StaticProjectConfigManagerFromPayload"))
218-
219-
if err != nil {
220-
return nil, err
221-
}
223+
staticManager := config.NewStaticProjectConfigManager(f.SDKKey, config.WithInitialDatafile(f.Datafile), config.WithDatafileAccessToken(f.DatafileAccessToken))
222224

223-
configManager = staticConfigManager
225+
if staticManager == nil {
226+
return nil, errors.New("unable to initiate config manager")
224227
}
225228

226-
optlyClient, e := f.Client(
227-
WithConfigManager(configManager),
229+
optlyClient, err = f.Client(
230+
WithConfigManager(staticManager),
228231
WithBatchEventProcessor(event.DefaultBatchSize, event.DefaultEventQueueSize, event.DefaultEventFlushInterval),
229232
)
230233

231-
return optlyClient, e
234+
return optlyClient, err
232235
}

pkg/client/factory_test.go

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2019, Optimizely, Inc. and contributors *
2+
* Copyright 2019-2020, Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -25,10 +25,8 @@ import (
2525
"time"
2626

2727
"github.com/optimizely/go-sdk/pkg/config"
28-
"github.com/optimizely/go-sdk/pkg/config/datafileprojectconfig"
2928
"github.com/optimizely/go-sdk/pkg/decision"
3029
"github.com/optimizely/go-sdk/pkg/event"
31-
"github.com/optimizely/go-sdk/pkg/logging"
3230
"github.com/optimizely/go-sdk/pkg/metrics"
3331
"github.com/optimizely/go-sdk/pkg/utils"
3432

@@ -85,10 +83,19 @@ func TestClientWithPollingConfigManager(t *testing.T) {
8583
assert.NotNil(t, optimizelyClient.EventProcessor)
8684
}
8785

86+
func TestClientWithPollingConfigManagerDatafileAccessToken(t *testing.T) {
87+
factory := OptimizelyFactory{}
88+
89+
optimizelyClient, err := factory.Client(WithPollingConfigManagerDatafileAccessToken(time.Hour, nil, "some_token"))
90+
assert.NoError(t, err)
91+
assert.NotNil(t, optimizelyClient.ConfigManager)
92+
assert.NotNil(t, optimizelyClient.DecisionService)
93+
assert.NotNil(t, optimizelyClient.EventProcessor)
94+
}
8895
func TestClientWithProjectConfigManagerInOptions(t *testing.T) {
8996
factory := OptimizelyFactory{}
90-
projectConfig := datafileprojectconfig.DatafileProjectConfig{}
91-
configManager := config.NewStaticProjectConfigManager(projectConfig, logging.GetLogger("", ""))
97+
mockDatafile := []byte(`{"version":"4"}`)
98+
configManager := config.NewStaticProjectConfigManager("", config.WithInitialDatafile(mockDatafile))
9299

93100
optimizelyClient, err := factory.Client(WithConfigManager(configManager))
94101
assert.NoError(t, err)
@@ -99,10 +106,10 @@ func TestClientWithProjectConfigManagerInOptions(t *testing.T) {
99106

100107
func TestClientWithDecisionServiceAndEventProcessorInOptions(t *testing.T) {
101108
factory := OptimizelyFactory{}
102-
projectConfig := datafileprojectconfig.DatafileProjectConfig{}
103-
configManager := config.NewStaticProjectConfigManager(projectConfig, logging.GetLogger("", "StaticProjectConfigManager"))
109+
mockDatafile := []byte(`{"version":"4"}`)
110+
configManager := config.NewStaticProjectConfigManager("", config.WithInitialDatafile(mockDatafile))
104111
decisionService := new(MockDecisionService)
105-
processor := event.NewBatchEventProcessor(event.WithQueueSize(100),event.WithFlushInterval(100),
112+
processor := event.NewBatchEventProcessor(event.WithQueueSize(100), event.WithFlushInterval(100),
106113
event.WithQueue(event.NewInMemoryQueue(100)), event.WithEventDispatcher(&MockDispatcher{Events: []event.LogEvent{}}))
107114

108115
optimizelyClient, err := factory.Client(WithConfigManager(configManager), WithDecisionService(decisionService), WithEventProcessor(processor))
@@ -182,3 +189,15 @@ func TestClientMetrics(t *testing.T) {
182189
eventProcessor := optimizelyClient.EventProcessor.(*event.BatchEventProcessor)
183190
assert.NotNil(t, eventProcessor)
184191
}
192+
193+
func TestClientWithDatafileAccessToken(t *testing.T) {
194+
factory := OptimizelyFactory{SDKKey: "1212"}
195+
accessToken := "some_token"
196+
optimizelyClient, err := factory.Client(WithDatafileAccessToken(accessToken))
197+
assert.NoError(t, err)
198+
assert.NotNil(t, optimizelyClient.ConfigManager)
199+
assert.NotNil(t, optimizelyClient.DecisionService)
200+
assert.NotNil(t, optimizelyClient.EventProcessor)
201+
202+
assert.Equal(t, accessToken, factory.DatafileAccessToken)
203+
}

pkg/config/optimizely_config_test.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package config
1818

1919
import (
2020
"encoding/json"
21-
"github.com/optimizely/go-sdk/pkg/logging"
2221
"io/ioutil"
2322
"testing"
2423

@@ -51,10 +50,7 @@ func (s *OptimizelyConfigTestSuite) SetupTest() {
5150
s.Fail("error opening file " + dataFileName)
5251
}
5352

54-
projectMgr, err := NewStaticProjectConfigManagerFromPayload(dataFile, logging.GetLogger("", "NewStaticProjectConfigManagerFromPayload"))
55-
if err != nil {
56-
s.Fail("error creating project manager")
57-
}
53+
projectMgr := NewStaticProjectConfigManager("", WithInitialDatafile(dataFile))
5854

5955
s.projectConfig = projectMgr.projectConfig
6056

pkg/config/polling_manager.go

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2019, Optimizely, Inc. and contributors *
2+
* Copyright 2019-2020, Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -42,9 +42,12 @@ const ModifiedSince = "If-Modified-Since"
4242
// LastModified header key for response
4343
const LastModified = "Last-Modified"
4444

45-
// DatafileURLTemplate is used to construct the endpoint for retrieving the datafile from the CDN
45+
// DatafileURLTemplate is used to construct the endpoint for retrieving regular datafile from the CDN
4646
const DatafileURLTemplate = "https://cdn.optimizely.com/datafiles/%s.json"
4747

48+
// AuthDatafileURLTemplate is used to construct the endpoint for retrieving authenticated datafile from the CDN
49+
const AuthDatafileURLTemplate = "https://config.optimizely.com/datafiles/auth/%s.json"
50+
4851
// Err403Forbidden is 403Forbidden specific error
4952
var Err403Forbidden = errors.New("unable to fetch fresh datafile (consider rechecking SDK key), status code: 403 Forbidden")
5053

@@ -58,7 +61,8 @@ type PollingProjectConfigManager struct {
5861
pollingInterval time.Duration
5962
requester utils.Requester
6063
sdkKey string
61-
logger logging.OptimizelyLogProducer
64+
logger logging.OptimizelyLogProducer
65+
datafileAccessToken string
6266

6367
configLock sync.RWMutex
6468
err error
@@ -97,6 +101,13 @@ func WithInitialDatafile(datafile []byte) OptionFunc {
97101
}
98102
}
99103

104+
// WithDatafileAccessToken is an optional function, sets a passed datafile access token
105+
func WithDatafileAccessToken(datafileAccessToken string) OptionFunc {
106+
return func(p *PollingProjectConfigManager) {
107+
p.datafileAccessToken = datafileAccessToken
108+
}
109+
}
110+
100111
// SyncConfig downloads datafile and updates projectConfig
101112
func (cm *PollingProjectConfigManager) SyncConfig() {
102113
var e error
@@ -169,6 +180,10 @@ func (cm *PollingProjectConfigManager) SyncConfig() {
169180

170181
// Start starts the polling
171182
func (cm *PollingProjectConfigManager) Start(ctx context.Context) {
183+
if cm.pollingInterval <= 0 {
184+
cm.logger.Info("Polling Config Manager Disabled")
185+
return
186+
}
172187
cm.logger.Debug("Polling Config Manager Initiated")
173188
t := time.NewTicker(cm.pollingInterval)
174189
for {
@@ -182,48 +197,59 @@ func (cm *PollingProjectConfigManager) Start(ctx context.Context) {
182197
}
183198
}
184199

185-
// NewPollingProjectConfigManager returns an instance of the polling config manager with the customized configuration
186-
func NewPollingProjectConfigManager(sdkKey string, pollingMangerOptions ...OptionFunc) *PollingProjectConfigManager {
200+
func (cm *PollingProjectConfigManager) setAuthHeaderIfDatafileAccessTokenPresent() {
201+
if cm.datafileAccessToken != "" {
202+
headers := []utils.Header{{Name: "Content-Type", Value: "application/json"}, {Name: "Accept", Value: "application/json"}}
203+
headers = append(headers, utils.Header{Name: "Authorization", Value: "Bearer " + cm.datafileAccessToken})
204+
cm.requester = utils.NewHTTPRequester(logging.GetLogger(cm.sdkKey, "HTTPRequester"), utils.Headers(headers...))
205+
}
206+
}
187207

208+
func newConfigManager(sdkKey string, logger logging.OptimizelyLogProducer, configOptions ...OptionFunc) *PollingProjectConfigManager {
188209
pollingProjectConfigManager := PollingProjectConfigManager{
189-
notificationCenter: registry.GetNotificationCenter(sdkKey),
190-
pollingInterval: DefaultPollingInterval,
191-
requester: utils.NewHTTPRequester(logging.GetLogger(sdkKey, "HTTPRequester")),
192-
datafileURLTemplate: DatafileURLTemplate,
193-
sdkKey: sdkKey,
194-
logger: logging.GetLogger(sdkKey, "PollingProjectConfigManager"),
210+
notificationCenter: registry.GetNotificationCenter(sdkKey),
211+
pollingInterval: DefaultPollingInterval,
212+
requester: utils.NewHTTPRequester(logging.GetLogger(sdkKey, "HTTPRequester")),
213+
sdkKey: sdkKey,
214+
logger: logger,
195215
}
196216

197-
for _, opt := range pollingMangerOptions {
217+
for _, opt := range configOptions {
198218
opt(&pollingProjectConfigManager)
199219
}
200220

221+
if pollingProjectConfigManager.datafileURLTemplate == "" {
222+
if pollingProjectConfigManager.datafileAccessToken != "" {
223+
pollingProjectConfigManager.datafileURLTemplate = AuthDatafileURLTemplate
224+
} else {
225+
pollingProjectConfigManager.datafileURLTemplate = DatafileURLTemplate
226+
}
227+
}
228+
pollingProjectConfigManager.setAuthHeaderIfDatafileAccessTokenPresent()
229+
return &pollingProjectConfigManager
230+
}
231+
232+
// NewPollingProjectConfigManager returns an instance of the polling config manager with the customized configuration
233+
func NewPollingProjectConfigManager(sdkKey string, pollingMangerOptions ...OptionFunc) *PollingProjectConfigManager {
234+
235+
pollingProjectConfigManager := newConfigManager(sdkKey, logging.GetLogger(sdkKey, "PollingProjectConfigManager"), pollingMangerOptions...)
236+
201237
if len(pollingProjectConfigManager.initDatafile) > 0 {
202238
pollingProjectConfigManager.setInitialDatafile(pollingProjectConfigManager.initDatafile)
203239
} else {
204240
pollingProjectConfigManager.SyncConfig() // initial poll
205241
}
206-
return &pollingProjectConfigManager
242+
return pollingProjectConfigManager
207243
}
208244

209245
// NewAsyncPollingProjectConfigManager returns an instance of the async polling config manager with the customized configuration
210246
func NewAsyncPollingProjectConfigManager(sdkKey string, pollingMangerOptions ...OptionFunc) *PollingProjectConfigManager {
211247

212-
pollingProjectConfigManager := PollingProjectConfigManager{
213-
notificationCenter: registry.GetNotificationCenter(sdkKey),
214-
pollingInterval: DefaultPollingInterval,
215-
requester: utils.NewHTTPRequester(logging.GetLogger(sdkKey, "HTTPRequester")),
216-
datafileURLTemplate: DatafileURLTemplate,
217-
sdkKey: sdkKey,
218-
logger: logging.GetLogger(sdkKey, "PollingProjectConfigManager"),
219-
}
220-
221-
for _, opt := range pollingMangerOptions {
222-
opt(&pollingProjectConfigManager)
248+
pollingProjectConfigManager := newConfigManager(sdkKey, logging.GetLogger(sdkKey, "PollingProjectConfigManager"), pollingMangerOptions...)
249+
if len(pollingProjectConfigManager.initDatafile) > 0 {
250+
pollingProjectConfigManager.setInitialDatafile(pollingProjectConfigManager.initDatafile)
223251
}
224-
225-
pollingProjectConfigManager.setInitialDatafile(pollingProjectConfigManager.initDatafile)
226-
return &pollingProjectConfigManager
252+
return pollingProjectConfigManager
227253
}
228254

229255
// GetConfig returns the project config

0 commit comments

Comments
 (0)