Skip to content

Commit 0c96441

Browse files
authored
Add support for Account configuration (PBID-727, prebid#1395) (prebid#1426)
1 parent c867e6f commit 0c96441

File tree

29 files changed

+576
-86
lines changed

29 files changed

+576
-86
lines changed

analytics/core.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package analytics
22

33
import (
44
"github.com/mxmCherry/openrtb"
5+
"github.com/prebid/prebid-server/config"
56
"github.com/prebid/prebid-server/openrtb_ext"
67
"github.com/prebid/prebid-server/usersync"
78
)
@@ -28,6 +29,7 @@ type AuctionObject struct {
2829
Errors []error
2930
Request *openrtb.BidRequest
3031
Response *openrtb.BidResponse
32+
Account *config.Account
3133
}
3234

3335
//Loggable object of a transaction at /openrtb2/amp endpoint

config/accounts.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package config
2+
3+
// Account represents a publisher account configuration
4+
type Account struct {
5+
ID string `mapstructure:"id" json:"id"`
6+
Disabled bool `mapstructure:"disabled" json:"disabled"`
7+
CacheTTL DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"`
8+
}

config/config.go

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package config
22

33
import (
44
"bytes"
5+
"encoding/json"
56
"errors"
67
"fmt"
78
"net/url"
@@ -40,6 +41,7 @@ type Configuration struct {
4041
StoredRequests StoredRequests `mapstructure:"stored_requests"`
4142
StoredRequestsAMP StoredRequests `mapstructure:"stored_amp_req"`
4243
CategoryMapping StoredRequests `mapstructure:"category_mapping"`
44+
Accounts StoredRequests `mapstructure:"accounts"`
4345
// Note that StoredVideo refers to stored video requests, and has nothing to do with caching video creatives.
4446
StoredVideo StoredRequests `mapstructure:"stored_video_req"`
4547

@@ -65,6 +67,11 @@ type Configuration struct {
6567
BlacklistedAcctMap map[string]bool
6668
// Is publisher/account ID required to be submitted in the OpenRTB2 request
6769
AccountRequired bool `mapstructure:"account_required"`
70+
// AccountDefaults defines default settings for valid accounts that are partially defined
71+
// and provides a way to set global settings that can be overridden at account level.
72+
AccountDefaults Account `mapstructure:"account_defaults"`
73+
// accountDefaultsJSON is the internal serialized form of AccountDefaults used for json merge
74+
accountDefaultsJSON json.RawMessage
6875
// Local private file containing SSL certificates
6976
PemCertsFile string `mapstructure:"certificates_file"`
7077
// Custom headers to handle request timeouts from queueing infrastructure
@@ -106,10 +113,11 @@ func (c configErrors) Error() string {
106113
func (cfg *Configuration) validate() configErrors {
107114
var errs configErrors
108115
errs = cfg.AuctionTimeouts.validate(errs)
109-
errs = cfg.StoredRequests.validate("stored_req", errs)
110-
errs = cfg.StoredRequestsAMP.validate("stored_amp_req", errs)
111-
errs = cfg.CategoryMapping.validate("categories", errs)
112-
errs = cfg.StoredVideo.validate("stored_video_req", errs)
116+
errs = cfg.StoredRequests.validate(errs)
117+
errs = cfg.StoredRequestsAMP.validate(errs)
118+
errs = cfg.Accounts.validate(errs)
119+
errs = cfg.CategoryMapping.validate(errs)
120+
errs = cfg.StoredVideo.validate(errs)
113121
errs = cfg.Metrics.validate(errs)
114122
if cfg.MaxRequestSize < 0 {
115123
errs = append(errs, fmt.Errorf("cfg.max_request_size must be >= 0. Got %d", cfg.MaxRequestSize))
@@ -119,6 +127,9 @@ func (cfg *Configuration) validate() configErrors {
119127
errs = validateAdapters(cfg.Adapters, errs)
120128
errs = cfg.Debug.validate(errs)
121129
errs = cfg.ExtCacheURL.validate(errs)
130+
if cfg.AccountDefaults.Disabled {
131+
glog.Warning(`With account_defaults.disabled=true, host-defined accounts must exist and have "disabled":false. All other requests will be rejected.`)
132+
}
122133
return errs
123134
}
124135

@@ -589,6 +600,12 @@ func New(v *viper.Viper) (*Configuration, error) {
589600
return nil, err
590601
}
591602

603+
// Update account defaults and generate base json for patch
604+
c.AccountDefaults.CacheTTL = c.CacheURL.DefaultTTLs // comment this out to set explicitly in config
605+
if err := c.MarshalAccountDefaults(); err != nil {
606+
return nil, err
607+
}
608+
592609
// To look for a request's publisher_id in the NonStandardPublishers list in
593610
// O(1) time, we fill this hash table located in the NonStandardPublisherMap field of GDPR
594611
c.GDPR.NonStandardPublisherMap = make(map[string]int)
@@ -622,6 +639,20 @@ func New(v *viper.Viper) (*Configuration, error) {
622639
return &c, nil
623640
}
624641

642+
// MarshalAccountDefaults compiles AccountDefaults into the JSON format used for merge patch
643+
func (cfg *Configuration) MarshalAccountDefaults() error {
644+
var err error
645+
if cfg.accountDefaultsJSON, err = json.Marshal(cfg.AccountDefaults); err != nil {
646+
glog.Warningf("converting %+v to json: %v", cfg.AccountDefaults, err)
647+
}
648+
return err
649+
}
650+
651+
// AccountDefaultsJSON returns the precompiled JSON form of account_defaults
652+
func (cfg *Configuration) AccountDefaultsJSON() json.RawMessage {
653+
return cfg.accountDefaultsJSON
654+
}
655+
625656
//Allows for protocol relative URL if scheme is empty
626657
func (cfg *Cache) GetBaseURL() string {
627658
cfg.Scheme = strings.ToLower(cfg.Scheme)
@@ -843,6 +874,10 @@ func SetupViper(v *viper.Viper, filename string) {
843874
v.SetDefault("stored_video_req.http_events.refresh_rate_seconds", 0)
844875
v.SetDefault("stored_video_req.http_events.timeout_ms", 0)
845876

877+
v.SetDefault("accounts.filesystem.enabled", false)
878+
v.SetDefault("accounts.filesystem.directorypath", "./stored_requests/data/by_id")
879+
v.SetDefault("accounts.in_memory_cache.type", "none")
880+
846881
for _, bidder := range openrtb_ext.BidderMap {
847882
setBidderDefaults(v, strings.ToLower(string(bidder)))
848883
}
@@ -976,6 +1011,7 @@ func SetupViper(v *viper.Viper, filename string) {
9761011
v.SetDefault("blacklisted_apps", []string{""})
9771012
v.SetDefault("blacklisted_accts", []string{""})
9781013
v.SetDefault("account_required", false)
1014+
v.SetDefault("account_defaults.disabled", false)
9791015
v.SetDefault("certificates_file", "")
9801016
v.SetDefault("auto_gen_source_tid", true)
9811017

config/config_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package config
22

33
import (
44
"bytes"
5+
"errors"
56
"net"
67
"os"
78
"strings"
@@ -466,6 +467,10 @@ func TestValidConfig(t *testing.T) {
466467
CategoryMapping: StoredRequests{
467468
Files: FileFetcherConfig{Enabled: true},
468469
},
470+
Accounts: StoredRequests{
471+
Files: FileFetcherConfig{Enabled: true},
472+
InMemoryCache: InMemoryCache{Type: "none"},
473+
},
469474
}
470475

471476
resolvedStoredRequestsConfig(&cfg)
@@ -640,6 +645,18 @@ func TestValidateDebug(t *testing.T) {
640645
assert.NotNil(t, err, "cfg.debug.timeout_notification.sampling_rate should not be allowed to be greater than 1.0, but it was allowed")
641646
}
642647

648+
func TestValidateAccountsConfigRestrictions(t *testing.T) {
649+
cfg := newDefaultConfig(t)
650+
cfg.Accounts.Files.Enabled = true
651+
cfg.Accounts.HTTP.Endpoint = "http://localhost"
652+
cfg.Accounts.Postgres.ConnectionInfo.Database = "accounts"
653+
654+
errs := cfg.validate()
655+
assert.Len(t, errs, 2)
656+
assert.Contains(t, errs, errors.New("accounts.http: retrieving accounts via http not available, use accounts.files"))
657+
assert.Contains(t, errs, errors.New("accounts.postgres: retrieving accounts via postgres not available, use accounts.files"))
658+
}
659+
643660
func newDefaultConfig(t *testing.T) *Configuration {
644661
v := viper.New()
645662
SetupViper(v, "")

config/stored_requests.go

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,20 @@ const (
1818
CategoryDataType DataType = "Category"
1919
VideoDataType DataType = "Video"
2020
AMPRequestDataType DataType = "AMP Request"
21+
AccountDataType DataType = "Account"
2122
)
2223

24+
// Section returns the config section this type is defined in
25+
func (sr *StoredRequests) Section() string {
26+
return map[DataType]string{
27+
RequestDataType: "stored_requests",
28+
CategoryDataType: "categories",
29+
VideoDataType: "stored_video_req",
30+
AMPRequestDataType: "stored_amp_req",
31+
AccountDataType: "accounts",
32+
}[sr.dataType]
33+
}
34+
2335
func (sr *StoredRequests) DataType() DataType {
2436
return sr.dataType
2537
}
@@ -109,34 +121,42 @@ func resolvedStoredRequestsConfig(cfg *Configuration) {
109121
cfg.StoredRequestsAMP.dataType = AMPRequestDataType
110122
cfg.StoredVideo.dataType = VideoDataType
111123
cfg.CategoryMapping.dataType = CategoryDataType
124+
cfg.Accounts.dataType = AccountDataType
112125
return
113126
}
114127

115-
func (cfg *StoredRequests) validate(section string, errs configErrors) configErrors {
116-
errs = cfg.Postgres.validate(section, errs)
128+
func (cfg *StoredRequests) validate(errs configErrors) configErrors {
129+
if cfg.DataType() == AccountDataType && cfg.HTTP.Endpoint != "" {
130+
errs = append(errs, fmt.Errorf("%s.http: retrieving accounts via http not available, use accounts.files", cfg.Section()))
131+
}
132+
if cfg.DataType() == AccountDataType && cfg.Postgres.ConnectionInfo.Database != "" {
133+
errs = append(errs, fmt.Errorf("%s.postgres: retrieving accounts via postgres not available, use accounts.files", cfg.Section()))
134+
} else {
135+
errs = cfg.Postgres.validate(cfg.Section(), errs)
136+
}
117137

118138
// Categories do not use cache so none of the following checks apply
119-
if cfg.dataType == CategoryDataType {
139+
if cfg.DataType() == CategoryDataType {
120140
return errs
121141
}
122142

123143
if cfg.InMemoryCache.Type == "none" {
124144
if cfg.CacheEvents.Enabled {
125-
errs = append(errs, fmt.Errorf("%s: cache_events must be disabled if in_memory_cache=none", section))
145+
errs = append(errs, fmt.Errorf("%s: cache_events must be disabled if in_memory_cache=none", cfg.Section()))
126146
}
127147

128148
if cfg.HTTPEvents.RefreshRate != 0 {
129-
errs = append(errs, fmt.Errorf("%s: http_events.refresh_rate_seconds must be 0 if in_memory_cache=none", section))
149+
errs = append(errs, fmt.Errorf("%s: http_events.refresh_rate_seconds must be 0 if in_memory_cache=none", cfg.Section()))
130150
}
131151

132152
if cfg.Postgres.PollUpdates.Query != "" {
133-
errs = append(errs, fmt.Errorf("%s: postgres.poll_for_updates.query must be empty if in_memory_cache=none", section))
153+
errs = append(errs, fmt.Errorf("%s: postgres.poll_for_updates.query must be empty if in_memory_cache=none", cfg.Section()))
134154
}
135155
if cfg.Postgres.CacheInitialization.Query != "" {
136-
errs = append(errs, fmt.Errorf("%s: postgres.initialize_caches.query must be empty if in_memory_cache=none", section))
156+
errs = append(errs, fmt.Errorf("%s: postgres.initialize_caches.query must be empty if in_memory_cache=none", cfg.Section()))
137157
}
138158
}
139-
errs = cfg.InMemoryCache.validate(section, errs)
159+
errs = cfg.InMemoryCache.validate(cfg.Section(), errs)
140160
return errs
141161
}
142162

endpoints/openrtb2/amp_auction.go

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ func NewAmpEndpoint(
4444
ex exchange.Exchange,
4545
validator openrtb_ext.BidderParamValidator,
4646
requestsById stored_requests.Fetcher,
47+
accounts stored_requests.AccountFetcher,
4748
categories stored_requests.CategoryFetcher,
4849
cfg *config.Configuration,
4950
met pbsmetrics.MetricsEngine,
@@ -53,7 +54,7 @@ func NewAmpEndpoint(
5354
bidderMap map[string]openrtb_ext.BidderName,
5455
) (httprouter.Handle, error) {
5556

56-
if ex == nil || validator == nil || requestsById == nil || cfg == nil || met == nil {
57+
if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || met == nil {
5758
return nil, errors.New("NewAmpEndpoint requires non-nil arguments.")
5859
}
5960

@@ -69,6 +70,7 @@ func NewAmpEndpoint(
6970
validator,
7071
requestsById,
7172
empty_fetcher.EmptyFetcher{},
73+
accounts,
7274
categories,
7375
cfg,
7476
met,
@@ -155,26 +157,30 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
155157
labels.CookieFlag = pbsmetrics.CookieFlagYes
156158
}
157159
labels.PubID = getAccountID(req.Site.Publisher)
158-
159-
// Blacklist account now that we have resolved the value
160-
if acctIdErr := validateAccount(deps.cfg, labels.PubID); acctIdErr != nil {
161-
errL = append(errL, acctIdErr)
162-
errCode := errortypes.ReadCode(acctIdErr)
163-
if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.BlacklistedAcctErrorCode {
164-
w.WriteHeader(http.StatusServiceUnavailable)
165-
labels.RequestStatus = pbsmetrics.RequestStatusBlacklisted
166-
} else {
167-
w.WriteHeader(http.StatusBadRequest)
168-
labels.RequestStatus = pbsmetrics.RequestStatusBadInput
160+
// Look up account now that we have resolved the pubID value
161+
account, acctIDErrs := deps.getAccount(ctx, labels.PubID)
162+
if len(acctIDErrs) > 0 {
163+
errL = append(errL, acctIDErrs...)
164+
httpStatus := http.StatusBadRequest
165+
metricsStatus := pbsmetrics.RequestStatusBadInput
166+
for _, er := range errL {
167+
errCode := errortypes.ReadCode(er)
168+
if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.BlacklistedAcctErrorCode {
169+
httpStatus = http.StatusServiceUnavailable
170+
metricsStatus = pbsmetrics.RequestStatusBlacklisted
171+
break
172+
}
169173
}
174+
w.WriteHeader(httpStatus)
175+
labels.RequestStatus = metricsStatus
170176
for _, err := range errortypes.FatalOnly(errL) {
171177
w.Write([]byte(fmt.Sprintf("Invalid request format: %s\n", err.Error())))
172178
}
173-
ao.Errors = append(ao.Errors, acctIdErr)
179+
ao.Errors = append(ao.Errors, acctIDErrs...)
174180
return
175181
}
176182

177-
response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, &deps.categories, nil)
183+
response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, account, &deps.categories, nil)
178184
ao.AuctionResponse = response
179185

180186
if err != nil {

0 commit comments

Comments
 (0)