Skip to content

Commit

Permalink
Ads.Cert Call Sign support (prebid#2241)
Browse files Browse the repository at this point in the history
  • Loading branch information
VeronikaSolovei9 authored Jul 27, 2022
1 parent 414adb9 commit 9bfc1ad
Show file tree
Hide file tree
Showing 24 changed files with 1,160 additions and 64 deletions.
25 changes: 18 additions & 7 deletions config/bidderinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,24 @@ type BidderInfos map[string]BidderInfo

// BidderInfo specifies all configuration for a bidder except for enabled status, endpoint, and extra information.
type BidderInfo struct {
Enabled bool // copied from adapter config for convenience. to be refactored.
Maintainer *MaintainerInfo `yaml:"maintainer"`
Capabilities *CapabilitiesInfo `yaml:"capabilities"`
ModifyingVastXmlAllowed bool `yaml:"modifyingVastXmlAllowed"`
Debug *DebugInfo `yaml:"debug"`
GVLVendorID uint16 `yaml:"gvlVendorID"`
Syncer *Syncer `yaml:"userSync"`
Enabled bool // copied from adapter config for convenience. to be refactored.
Maintainer *MaintainerInfo `yaml:"maintainer"`
Capabilities *CapabilitiesInfo `yaml:"capabilities"`
ModifyingVastXmlAllowed bool `yaml:"modifyingVastXmlAllowed"`
Debug *DebugInfo `yaml:"debug"`
GVLVendorID uint16 `yaml:"gvlVendorID"`
Syncer *Syncer `yaml:"userSync"`
Experiment BidderInfoExperiment `yaml:"experiment"`
}

// BidderInfoExperiment specifies non-production ready feature config for a bidder
type BidderInfoExperiment struct {
AdsCert BidderAdsCert `yaml:"adsCert"`
}

// BidderAdsCert enables Call Sign feature for bidder
type BidderAdsCert struct {
Enabled bool `yaml:"enabled"`
}

// MaintainerInfo specifies the support email address for a bidder.
Expand Down
11 changes: 11 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ type Configuration struct {
// GenerateRequestID overrides the bidrequest.id in an AMP Request or an App Stored Request with a generated UUID if set to true. The default is false.
GenerateRequestID bool `mapstructure:"generate_request_id"`
HostSChainNode *openrtb2.SupplyChainNode `mapstructure:"host_schain_node"`
// Experiment configures non-production ready features.
Experiment Experiment `mapstructure:"experiment"`
}

const MIN_COOKIE_SIZE_BYTES = 500
Expand Down Expand Up @@ -129,6 +131,7 @@ func (cfg *Configuration) validate(v *viper.Viper) []error {
if cfg.AccountDefaults.Events.Enabled {
glog.Warning(`account_defaults.events will currently not do anything as the feature is still under development. Please follow https://github.com/prebid/prebid-server/issues/1725 for more updates`)
}
errs = cfg.Experiment.validate(errs)
return errs
}

Expand Down Expand Up @@ -1203,6 +1206,14 @@ func SetupViper(v *viper.Viper, filename string) {

// Defaults for account_defaults.events.default_url
v.SetDefault("account_defaults.events.default_url", "https://PBS_HOST/event?t=##PBS-EVENTTYPE##&vtype=##PBS-VASTEVENT##&b=##PBS-BIDID##&f=i&a=##PBS-ACCOUNTID##&ts=##PBS-TIMESTAMP##&bidder=##PBS-BIDDER##&int=##PBS-INTEGRATION##&mt=##PBS-MEDIATYPE##&ch=##PBS-CHANNEL##&aid=##PBS-AUCTIONID##&l=##PBS-LINEID##")

v.SetDefault("experiment.adscert.mode", "off")
v.SetDefault("experiment.adscert.inprocess.origin", "")
v.SetDefault("experiment.adscert.inprocess.key", "")
v.SetDefault("experiment.adscert.inprocess.domain_check_interval_seconds", 30)
v.SetDefault("experiment.adscert.inprocess.domain_renewal_interval_seconds", 30)
v.SetDefault("experiment.adscert.remote.url", "")
v.SetDefault("experiment.adscert.remote.signing_timeout_ms", 5)
}

func migrateConfig(v *viper.Viper) {
Expand Down
25 changes: 25 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@ func TestDefaults(t *testing.T) {
cmpStrings(t, "stored_requests.filesystem.directorypath", "./stored_requests/data/by_id", cfg.StoredRequests.Files.Path)
cmpBools(t, "auto_gen_source_tid", cfg.AutoGenSourceTID, true)
cmpBools(t, "generate_bid_id", cfg.GenerateBidID, false)
cmpStrings(t, "experiment.adscert.mode", cfg.Experiment.AdCerts.Mode, "off")
cmpStrings(t, "experiment.adscert.inprocess.origin", cfg.Experiment.AdCerts.InProcess.Origin, "")
cmpStrings(t, "experiment.adscert.inprocess.key", cfg.Experiment.AdCerts.InProcess.PrivateKey, "")
cmpInts(t, "experiment.adscert.inprocess.domain_check_interval_seconds", cfg.Experiment.AdCerts.InProcess.DNSCheckIntervalInSeconds, 30)
cmpInts(t, "experiment.adscert.inprocess.domain_renewal_interval_seconds", cfg.Experiment.AdCerts.InProcess.DNSRenewalIntervalInSeconds, 30)
cmpStrings(t, "experiment.adscert.remote.url", cfg.Experiment.AdCerts.Remote.Url, "")
cmpInts(t, "experiment.adscert.remote.signing_timeout_ms", cfg.Experiment.AdCerts.Remote.SigningTimeoutMs, 5)
cmpNils(t, "host_schain_node", cfg.HostSChainNode)

//Assert purpose VendorExceptionMap hash tables were built correctly
Expand Down Expand Up @@ -384,6 +391,17 @@ host_schain_node:
sid: "00001"
rid: "BidRequest"
hp: 1
experiment:
adscert:
mode: inprocess
inprocess:
origin: "http://test.com"
key: "ABC123"
domain_check_interval_seconds: 40
domain_renewal_interval_seconds : 60
remote:
url: ""
signing_timeout_ms: 10
`)

var adapterExtraInfoConfig = []byte(`
Expand Down Expand Up @@ -642,6 +660,13 @@ func TestFullConfig(t *testing.T) {
cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[1], "2222::/16")
cmpBools(t, "generate_bid_id", cfg.GenerateBidID, true)
cmpStrings(t, "debug.override_token", cfg.Debug.OverrideToken, "")
cmpStrings(t, "experiment.adscert.mode", cfg.Experiment.AdCerts.Mode, "inprocess")
cmpStrings(t, "experiment.adscert.inprocess.origin", cfg.Experiment.AdCerts.InProcess.Origin, "http://test.com")
cmpStrings(t, "experiment.adscert.inprocess.key", cfg.Experiment.AdCerts.InProcess.PrivateKey, "ABC123")
cmpInts(t, "experiment.adscert.inprocess.domain_check_interval_seconds", cfg.Experiment.AdCerts.InProcess.DNSCheckIntervalInSeconds, 40)
cmpInts(t, "experiment.adscert.inprocess.domain_renewal_interval_seconds", cfg.Experiment.AdCerts.InProcess.DNSRenewalIntervalInSeconds, 60)
cmpStrings(t, "experiment.adscert.remote.url", cfg.Experiment.AdCerts.Remote.Url, "")
cmpInts(t, "experiment.adscert.remote.signing_timeout_ms", cfg.Experiment.AdCerts.Remote.SigningTimeoutMs, 10)
}

func TestUnmarshalAdapterExtraInfo(t *testing.T) {
Expand Down
91 changes: 91 additions & 0 deletions config/experiment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package config

import (
"errors"
"fmt"
"net/url"
)

var (
ErrSignerModeIncorrect = errors.New("signer mode is not specified, specify 'off', 'inprocess' or 'remote'")
ErrInProcessSignerInvalidPrivateKey = errors.New("private key for inprocess signer cannot be empty")

ErrMsgInProcessSignerInvalidURL = "invalid url for inprocess signer"
ErrMsgInProcessSignerInvalidDNSRenewalInterval = "invalid dns renewal interval for inprocess signer"
ErrMsgInProcessSignerInvalidDNSCheckInterval = "invalid dns check interval for inprocess signer"
ErrMsgInvalidRemoteSignerURL = "invalid url for remote signer"
ErrMsgInvalidRemoteSignerSigningTimeout = "invalid signing timeout for remote signer"
)

const (
AdCertsSignerModeOff = "off"
AdCertsSignerModeInprocess = "inprocess"
AdCertsSignerModeRemote = "remote"
)

// Experiment defines if experimental features are available
type Experiment struct {
AdCerts ExperimentAdsCert `mapstructure:"adscert"`
}

// ExperimentAdsCert configures and enables functionality to generate and send Ads Cert Auth header to bidders
type ExperimentAdsCert struct {
Mode string `mapstructure:"mode"`
InProcess AdsCertInProcess `mapstructure:"inprocess"`
Remote AdsCertRemote `mapstructure:"remote"`
}

// AdsCertInProcess configures data to sign requests using ads certs library in core PBS logic
type AdsCertInProcess struct {
// Origin is ads.cert hostname for the originating party
Origin string `mapstructure:"origin"`
// PrivateKey is a base-64 encoded private key.
PrivateKey string `mapstructure:"key"`
// DNSCheckIntervalInSeconds specifies frequency to check origin _delivery._adscert and _adscert subdomains, used for indexing data, default: 30
DNSCheckIntervalInSeconds int `mapstructure:"domain_check_interval_seconds"`
// DNSRenewalIntervalInSeconds specifies frequency to renew origin _delivery._adscert and _adscert subdomains, used for indexing data, default: 30
DNSRenewalIntervalInSeconds int `mapstructure:"domain_renewal_interval_seconds"`
}

// AdsCertRemote configures data to sign requests using remote signatory service
type AdsCertRemote struct {
// Url is the address of gRPC server that will create a call signature
Url string `mapstructure:"url"`
// SigningTimeoutMs specifies how long this client will wait for signing to finish before abandoning
SigningTimeoutMs int `mapstructure:"signing_timeout_ms"`
}

func (cfg *Experiment) validate(errs []error) []error {
if len(cfg.AdCerts.Mode) == 0 {
return errs
}
if !(cfg.AdCerts.Mode == AdCertsSignerModeOff ||
cfg.AdCerts.Mode == AdCertsSignerModeInprocess ||
cfg.AdCerts.Mode == AdCertsSignerModeRemote) {
return append(errs, ErrSignerModeIncorrect)
}
if cfg.AdCerts.Mode == AdCertsSignerModeInprocess {
_, err := url.ParseRequestURI(cfg.AdCerts.InProcess.Origin)
if err != nil {
errs = append(errs, fmt.Errorf("%s: %s", ErrMsgInProcessSignerInvalidURL, cfg.AdCerts.InProcess.Origin))
}
if len(cfg.AdCerts.InProcess.PrivateKey) == 0 {
errs = append(errs, ErrInProcessSignerInvalidPrivateKey)
}
if cfg.AdCerts.InProcess.DNSRenewalIntervalInSeconds <= 0 {
errs = append(errs, fmt.Errorf("%s: %d", ErrMsgInProcessSignerInvalidDNSRenewalInterval, cfg.AdCerts.InProcess.DNSRenewalIntervalInSeconds))
}
if cfg.AdCerts.InProcess.DNSCheckIntervalInSeconds <= 0 {
errs = append(errs, fmt.Errorf("%s: %d", ErrMsgInProcessSignerInvalidDNSCheckInterval, cfg.AdCerts.InProcess.DNSCheckIntervalInSeconds))
}
} else if cfg.AdCerts.Mode == AdCertsSignerModeRemote {
_, err := url.ParseRequestURI(cfg.AdCerts.Remote.Url)
if err != nil {
errs = append(errs, fmt.Errorf("%s: %s", ErrMsgInvalidRemoteSignerURL, cfg.AdCerts.Remote.Url))
}
if cfg.AdCerts.Remote.SigningTimeoutMs <= 0 {
errs = append(errs, fmt.Errorf("%s: %d", ErrMsgInvalidRemoteSignerSigningTimeout, cfg.AdCerts.Remote.SigningTimeoutMs))
}
}
return errs
}
134 changes: 134 additions & 0 deletions config/experiment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package config

import (
"errors"
"github.com/stretchr/testify/assert"
"testing"
)

func TestExperimentValidate(t *testing.T) {
testCases := []struct {
desc string
data Experiment
expectErrors bool
expectedErrors []error
}{
{
desc: "Remote signer config: invalid remote url passed to config",
data: Experiment{
AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeRemote, Remote: AdsCertRemote{Url: "test@com", SigningTimeoutMs: 5}},
},
expectErrors: true,
expectedErrors: []error{errors.New("invalid url for remote signer: test@com")},
},
{
desc: "Remote signer config: invalid SigningTimeoutMs passed to config",
data: Experiment{
AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeRemote, Remote: AdsCertRemote{Url: "http://test.com", SigningTimeoutMs: 0}},
},
expectErrors: true,
expectedErrors: []error{errors.New("invalid signing timeout for remote signer: 0")},
},
{
desc: "Remote signer config: invalid URL and SigningTimeoutMs passed to config",
data: Experiment{
AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeRemote, Remote: AdsCertRemote{Url: "test@com", SigningTimeoutMs: 0}},
},
expectErrors: true,
expectedErrors: []error{errors.New("invalid url for remote signer: test@com"),
errors.New("invalid signing timeout for remote signer: 0")},
},
{
desc: "Remote signer config: valid URL and SigningTimeoutMs passed to config",
data: Experiment{
AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeRemote, Remote: AdsCertRemote{Url: "http://test.com", SigningTimeoutMs: 5}},
},
expectErrors: false,
expectedErrors: []error{},
},
{
desc: "Experiment config: experiment config is empty",
data: Experiment{
AdCerts: ExperimentAdsCert{Mode: ""},
},
expectErrors: false,
expectedErrors: []error{},
},
{
desc: "Experiment config: experiment config is off",
data: Experiment{
AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeOff},
},
expectErrors: false,
expectedErrors: []error{},
},
{
desc: "Experiment config: experiment config is init with a wrong value",
data: Experiment{
AdCerts: ExperimentAdsCert{Mode: "test"},
},
expectErrors: true,
expectedErrors: []error{ErrSignerModeIncorrect},
},
{
desc: "Inprocess signer config: valid config",
data: Experiment{
AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeInprocess, InProcess: AdsCertInProcess{Origin: "http://test.com", PrivateKey: "pk", DNSCheckIntervalInSeconds: 10, DNSRenewalIntervalInSeconds: 10}},
},
expectErrors: false,
expectedErrors: []error{},
},
{
desc: "Inprocess signer config: invaild origin url passed to config",
data: Experiment{
AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeInprocess, InProcess: AdsCertInProcess{Origin: "test@com", PrivateKey: "pk", DNSCheckIntervalInSeconds: 10, DNSRenewalIntervalInSeconds: 10}},
},
expectErrors: true,
expectedErrors: []error{errors.New("invalid url for inprocess signer: test@com")},
},
{
desc: "Inprocess signer config: empty PK passed to config",
data: Experiment{
AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeInprocess, InProcess: AdsCertInProcess{Origin: "http://test.com", PrivateKey: "", DNSCheckIntervalInSeconds: 10, DNSRenewalIntervalInSeconds: 10}},
},
expectErrors: true,
expectedErrors: []error{ErrInProcessSignerInvalidPrivateKey},
},
{
desc: "Inprocess signer config: negative dns check interval passed to config",
data: Experiment{
AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeInprocess, InProcess: AdsCertInProcess{Origin: "http://test.com", PrivateKey: "pk", DNSCheckIntervalInSeconds: -10, DNSRenewalIntervalInSeconds: 10}},
},
expectErrors: true,
expectedErrors: []error{errors.New("invalid dns check interval for inprocess signer: -10")},
},
{
desc: "Inprocess signer config: zero dns check interval passed to config",
data: Experiment{
AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeInprocess, InProcess: AdsCertInProcess{Origin: "http://test.com", PrivateKey: "pk", DNSCheckIntervalInSeconds: 10, DNSRenewalIntervalInSeconds: 0}},
},
expectErrors: true,
expectedErrors: []error{errors.New("invalid dns renewal interval for inprocess signer: 0")},
},
{
desc: "Inprocess signer config: all config parameters are invalid",
data: Experiment{
AdCerts: ExperimentAdsCert{Mode: AdCertsSignerModeInprocess, InProcess: AdsCertInProcess{Origin: "test@com", PrivateKey: "", DNSCheckIntervalInSeconds: -10, DNSRenewalIntervalInSeconds: 0}},
},
expectErrors: true,
expectedErrors: []error{
errors.New("invalid url for inprocess signer: test@com"),
ErrInProcessSignerInvalidPrivateKey,
errors.New("invalid dns check interval for inprocess signer: -10"),
errors.New("invalid dns renewal interval for inprocess signer: 0")},
},
}
for _, test := range testCases {
errs := test.data.validate([]error{})
if test.expectErrors {
assert.ElementsMatch(t, test.expectedErrors, errs, "Test case threw unexpected errors. Desc: %s \n", test.desc)
} else {
assert.Empty(t, test.expectedErrors, "Test case should not return errors. Desc: %s \n", test.desc)
}
}
}
Loading

0 comments on commit 9bfc1ad

Please sign in to comment.