diff --git a/config/bidderinfo.go b/config/bidderinfo.go index 0892c0f1685..d6c4ceaf8c4 100644 --- a/config/bidderinfo.go +++ b/config/bidderinfo.go @@ -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. diff --git a/config/config.go b/config/config.go index e49d8426dfc..d0dbb6f3477 100644 --- a/config/config.go +++ b/config/config.go @@ -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 @@ -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 } @@ -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) { diff --git a/config/config_test.go b/config/config_test.go index fbd9a9cbc3b..ca02550e019 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -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 @@ -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(` @@ -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) { diff --git a/config/experiment.go b/config/experiment.go new file mode 100644 index 00000000000..e57825962ec --- /dev/null +++ b/config/experiment.go @@ -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 +} diff --git a/config/experiment_test.go b/config/experiment_test.go new file mode 100644 index 00000000000..e61e199a79f --- /dev/null +++ b/config/experiment_test.go @@ -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) + } + } +} diff --git a/docs/adscertsigner.md b/docs/adscertsigner.md new file mode 100644 index 00000000000..b16adb6ecfe --- /dev/null +++ b/docs/adscertsigner.md @@ -0,0 +1,90 @@ +##Ads Cert + +Ads Cert is an experimental feature to support Ads.Cert 2.0 in Prebid Server. +The ads.cert protocol provides a standard method for distributing public keys so that other ads +ecosystem participants can find them and use them within these key exchange and message +authentication processes. To simplify this process, we use the domain name system (DNS) to +distribute public keys. + +Detailed Ads.Cert 2.0 specification is published on the [IAB Tech Lab ads.cert website](https://iabtechlab.com/ads-cert). + + +###General set up +According to [Ads Cert Authenticated Connections protocol](https://iabtechlab.com/wp-content/uploads/2021/09/3-ads-cert-authenticated-connections-pc.pdf) +the requested domain requires to support Call Sign Internet domain established for Public keys publishing. +In case origin URL is **bidder.com** then two subdomains has to be configured to return TXT records: + +`_adscert.bidder.com` - returns record in next format: +`v=adpf a=bidder.com` + +`_delivery._adscert.bidder.com` - returns record that looks like this: +`v=adcrtd k=x25519 h=sha256 p=w8f3160kEklY-nKuxogvn5PsZQLfkWWE0gUq_4JfFm8` + +For testing purposes please use this test domain (subscription will expire in May 2023): +`adscertdelivery.com`. To check data it returns use any online tool ([like this](https://mxtoolbox.com/SuperTool.aspx), select TXT lookup) to read TXT records: +`_delivery._adscert.adscertdelivery.com` and `_adscert.adscertdelivery.com` + +Or just run cli command: +```dig txt _delivery._adscert.adscertdelivery.com``` + +Public key returned in `_delivery._adscert.adscertdelivery.com` was generated using [OSS repository](https://github.com/IABTechLab/adscert). +From the project root compile sources and run `go run . basicinsecurekeygen`. This will return randomly generated private and public keys and the entire value for `_delivery._adscert.adscertdelivery.com` record. + +Private key for public key published under `_delivery._adscert.adscertdelivery.com`: +``` +Randomly generated key pair +Public key: HweE1-dFJPjHO4C34QXq6myhtMuyi4X0T2rUolVzQig +Private key: U6KBGSEQ5kuMn3s_ohxYbmdmG7Xoos9hR3fJ_dDOi6Q +DNS TXT Entry: "v=adcrtd k=x25519 h=sha256 p=HweE1-dFJPjHO4C34QXq6myhtMuyi4X0T2rUolVzQig" +``` + +If everything configured correctly then `X-Ads-Cert-Auth` header will be sent to bidder. Detailed information about content of the header value can be found in Ads Cert Authenticated Connections protocol specification. + +###Prebid Server set up +Current Prebid Server implementation supports in-process and remote signing approach. + +####In-Process signer +To enable AdsCerts next configurations should be specified: + +Host config, can be set using env variables or yaml config, use proper format: +```json +"experiment": { + "adscert": { + "mode": "inprocess", + "inprocess": { + "origin": "http://adscertdelivery.com", + "key": "U6KBGSEQ5kuMn3s_ohxYbmdmG7Xoos9hR3fJ_dDOi6Q", + "domain_check_interval_seconds": 30, + "domain_renewal_interval_seconds": 30 + } + } + } +``` +####Remote signer +To use this approach standalone GRPC server should be available. +One way to do this is to run in locally. For this checkout [AdsCert OSS](https://github.com/IABTechLab/adscert) and navigate to https://github.com/IABTechLab/adscert/blob/main/cmd/server/main.go file. +Modify L17, set "origin" to `adscertdelivery.com`, make sure ports 3000 and 3001 are available and run main function. +In Prebid Server configs set parameters for this server: +```json +"experiment": { + "adscert": { + "mode": "remote", + "remote": { + "url": "localhost:3000", + "signing_timeout_ms": 5 + } + } + } +``` + +####General Prebid Server set up +Workaround for bidders that don't have Call Signs support yet: in configs modify bidder URL to `http://adscertdelivery.com/openrtb2?prebid_disabled=1`. In this case this bidder will not return bids, because this endpoint doesn't exist, but it will imitate support of Call Signs. Bidder parameters still should be valid. + +Every bidder by default doesn't support AdsCert. Some bidders cannot handle unsupported headers properly. To enable this feature add next config to {bidder}.yaml file: +`experiment.adsCert.enabled: true`. With this config bidder will receive `X-Ads-Cert-Auth` header even if this is not the only bidder in request. + +Request extension should have `request.ext.prebid.experiment.adscert.enabled: true` + +###Issue to fix: +- After server start up the very first request doesn't have `X-Ads-Cert-Auth` header. But it works every time after the first request. +- Bidders that don't support CallSigns don't receive a default `X-Ads-Cert-Auth` header \ No newline at end of file diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go index 02f11bc56da..9c83b24b08c 100644 --- a/endpoints/openrtb2/auction_benchmark_test.go +++ b/endpoints/openrtb2/auction_benchmark_test.go @@ -3,6 +3,7 @@ package openrtb2 import ( "bytes" "fmt" + "github.com/prebid/prebid-server/experiment/adscert" "io/ioutil" "net/http" "net/http/httptest" @@ -95,6 +96,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { tcf2ConfigBuilder, currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), empty_fetcher.EmptyFetcher{}, + &adscert.NilSigner{}, ) endpoint, _ := NewEndpoint( diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go index c54d8b82ed1..82fd78faad4 100644 --- a/endpoints/openrtb2/test_utils.go +++ b/endpoints/openrtb2/test_utils.go @@ -26,6 +26,7 @@ import ( "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/exchange" + "github.com/prebid/prebid-server/experiment/adscert" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/metrics" metricsConfig "github.com/prebid/prebid-server/metrics/config" @@ -1119,6 +1120,7 @@ func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.Bid tcf2ConfigBuilder, mockCurrencyConverter, mockFetcher, + &adscert.NilSigner{}, ), mockBidServersArray } diff --git a/exchange/bidder.go b/exchange/bidder.go index d6ece01c908..eb53a14b21c 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -16,6 +16,7 @@ import ( "github.com/golang/glog" "github.com/prebid/prebid-server/config/util" "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/experiment/adscert" "github.com/prebid/prebid-server/version" nativeRequests "github.com/mxmCherry/openrtb/v16/native1/request" @@ -51,7 +52,15 @@ type AdaptedBidder interface { // // Any errors will be user-facing in the API. // Error messages should help publishers understand what might account for "bad" bids. - requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustments map[string]float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool, alternateBidderCodes config.AlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) + requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes config.AlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) +} + +//bidRequestOptions holds additional options for bid request execution to maintain clean code and reasonable number of parameters +type bidRequestOptions struct { + accountDebugAllowed bool + headerDebugAllowed bool + addCallSignHeader bool + bidAdjustments map[string]float64 } const ImpIdReqBody = "Stored bid response for impression id: " @@ -136,7 +145,7 @@ type bidderAdapterConfig struct { DebugInfo config.DebugInfo } -func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustments map[string]float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool, alternateBidderCodes config.AlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) { +func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes config.AlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) { var reqData []*adapters.RequestData var errs []error @@ -166,6 +175,17 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde if reqInfo.GlobalPrivacyControlHeader == "1" { reqData[i].Headers.Add("Sec-GPC", reqInfo.GlobalPrivacyControlHeader) } + if bidRequestOptions.addCallSignHeader { + signatureMessage, err := adsCertSigner.Sign(reqData[i].Uri, reqData[i].Body) + if err != nil { + //add metrics here + errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("AdsCert signer is enabled but cannot sign the request: %s", err.Error())}) + } + if err == nil && len(signatureMessage) > 0 { + reqData[i].Headers.Add(adscert.SignHeader, signatureMessage) + } + } + } // Make any HTTP requests in parallel. // If the bidder only needs to make one, save some cycles by just using the current one. @@ -213,10 +233,10 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde // - headerDebugAllowed (debug override header specified correct) - it overrides all other debug restrictions // - account debug is allowed // - bidder debug is allowed - if headerDebugAllowed { + if bidRequestOptions.headerDebugAllowed { seatBidMap[bidderRequest.BidderName].httpCalls = append(seatBidMap[bidderRequest.BidderName].httpCalls, makeExt(httpInfo)) } else { - if accountDebugAllowed { + if bidRequestOptions.accountDebugAllowed { if bidder.config.DebugInfo.Allow { seatBidMap[bidderRequest.BidderName].httpCalls = append(seatBidMap[bidderRequest.BidderName].httpCalls, makeExt(httpInfo)) } else { @@ -310,9 +330,9 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde } adjustmentFactor := 1.0 - if givenAdjustment, ok := bidAdjustments[bidderName.String()]; ok { + if givenAdjustment, ok := bidRequestOptions.bidAdjustments[bidderName.String()]; ok { adjustmentFactor = givenAdjustment - } else if givenAdjustment, ok := bidAdjustments[bidderRequest.BidderName.String()]; ok { + } else if givenAdjustment, ok := bidRequestOptions.bidAdjustments[bidderRequest.BidderName.String()]; ok { adjustmentFactor = givenAdjustment } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index b11afa789ca..c6fc16269a4 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -24,6 +24,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/experiment/adscert" "github.com/prebid/prebid-server/metrics" metricsConfig "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -99,7 +100,13 @@ func TestSingleBidder(t *testing.T) { BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, BidderName: "test", } - seatBids, errs := bidder.requestBid(ctx, bidderReq, bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false, config.AlternateBidderCodes{}) + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{}) assert.Len(t, seatBids, 1) seatBid := seatBids[0] @@ -189,7 +196,13 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { BidderName: "test", } bidAdjustments := map[string]float64{"test": 1} - seatBids, errs := bidder.requestBid(ctx, bidderReq, bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false, config.AlternateBidderCodes{}) + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{}) expectedHttpCalls := []*openrtb_ext.ExtHttpCall{ { @@ -235,7 +248,13 @@ func TestSetGPCHeader(t *testing.T) { BidderName: "test", } bidAdjustments := map[string]float64{"test": 1} - seatBids, errs := bidder.requestBid(ctx, bidderReq, bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false, config.AlternateBidderCodes{}) + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{}) expectedHttpCall := []*openrtb_ext.ExtHttpCall{ { @@ -279,7 +298,13 @@ func TestSetGPCHeaderNil(t *testing.T) { BidderName: "test", } bidAdjustments := map[string]float64{"test": 1} - seatBids, errs := bidder.requestBid(ctx, bidderReq, bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false, config.AlternateBidderCodes{}) + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{}) expectedHttpCall := []*openrtb_ext.ExtHttpCall{ { @@ -343,7 +368,13 @@ func TestMultiBidder(t *testing.T) { BidderName: "test", } bidAdjustments := map[string]float64{"test": 1.0} - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true, config.AlternateBidderCodes{}) + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: true, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{}) if len(seatBids) != 1 { t.Fatalf("SeatBid should exist, because bids exist.") @@ -713,11 +744,15 @@ func TestMultiCurrencies(t *testing.T) { seatBids, errs := bidder.requestBid( context.Background(), bidderReq, - bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, - true, - true, + &adscert.NilSigner{}, + bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: true, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + }, config.AlternateBidderCodes{}, ) assert.Len(t, seatBids, 1) @@ -867,11 +902,15 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { seatBids, errs := bidder.requestBid( context.Background(), bidderReq, - bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, - true, - true, + &adscert.NilSigner{}, + bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: true, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + }, config.AlternateBidderCodes{}, ) assert.Len(t, seatBids, 1) @@ -1040,11 +1079,15 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { seatBids, errs := bidder.requestBid( context.Background(), bidderReq, - bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, - true, - false, + &adscert.NilSigner{}, + bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + }, config.AlternateBidderCodes{}, ) assert.Len(t, seatBids, 1) @@ -1352,11 +1395,15 @@ func TestMobileNativeTypes(t *testing.T) { seatBids, _ := bidder.requestBid( context.Background(), bidderReq, - bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, - true, - true, + &adscert.NilSigner{}, + bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: true, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + }, config.AlternateBidderCodes{}, ) assert.Len(t, seatBids, 1) @@ -1426,11 +1473,15 @@ func TestRequestBidsStoredBidResponses(t *testing.T) { seatBids, _ := bidder.requestBid( context.Background(), bidderReq, - bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, - true, - true, + &adscert.NilSigner{}, + bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: true, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + }, config.AlternateBidderCodes{}, ) assert.Len(t, seatBids, 1) @@ -1452,7 +1503,13 @@ func TestErrorReporting(t *testing.T) { BidderName: "test", } bidAdjustments := map[string]float64{"test": 1.0} - bids, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false, config.AlternateBidderCodes{}) + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + bids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{}) if bids != nil { t.Errorf("There should be no seatbid if no http requests are returned.") } @@ -1678,7 +1735,13 @@ func TestCallRecordAdapterConnections(t *testing.T) { BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, BidderName: openrtb_ext.BidderAppnexus, } - _, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true, config.AlternateBidderCodes{}) + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: true, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + _, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{}) // Assert no errors assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs) @@ -1876,6 +1939,56 @@ func TestPrepareStoredResponse(t *testing.T) { assert.Equal(t, []byte(`{"id": "resp_id1"}`), result.response.Body, "incorrect response body") } +func TestRequestBidsWithAdsCertsSigner(t *testing.T) { + respStatus := 200 + respBody := `{"bid":false}` + server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) + defer server.Close() + + requestHeaders := http.Header{} + requestHeaders.Add("Content-Type", "application/json") + + bidderImpl := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte(`{"key":"val"}`), + Headers: http.Header{}, + }, + bidResponse: nil, + } + bidderImpl.bidResponse = &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "bidId", + }, + BidType: openrtb_ext.BidTypeBanner, + DealPriority: 4, + }, + }, + } + + bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: false}) + currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + + bidderReq := BidderRequest{ + BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, + BidderName: "test", + } + ctx := context.Background() + bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 2.0} + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: false, + headerDebugAllowed: false, + addCallSignHeader: true, + bidAdjustments: bidAdjustments, + } + _, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, config.AlternateBidderCodes{}) + + assert.Empty(t, errs, "no errors should be returned") +} + func wrapWithBidderInfo(bidder adapters.Bidder) adapters.Bidder { bidderInfo := config.BidderInfo{ Enabled: true, @@ -2076,7 +2189,16 @@ func TestExtraBid(t *testing.T) { BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, BidderName: openrtb_ext.BidderPubmatic, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, map[string]float64{}, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, false, false, + + bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 2.0} + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: false, + headerDebugAllowed: false, + addCallSignHeader: true, + bidAdjustments: bidAdjustments, + } + + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, config.AlternateBidderCodes{ Enabled: true, Bidders: map[string]config.AdapterAlternateBidderCodes{ @@ -2179,7 +2301,15 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) { BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, BidderName: openrtb_ext.BidderPubmatic, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, map[string]float64{}, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, false, false, + bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 2.0} + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: false, + headerDebugAllowed: false, + addCallSignHeader: true, + bidAdjustments: bidAdjustments, + } + + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, config.AlternateBidderCodes{ Enabled: true, Bidders: map[string]config.AdapterAlternateBidderCodes{ @@ -2279,7 +2409,15 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { string(openrtb_ext.BidderPubmatic): 2, string(openrtb_ext.BidderGroupm): 3, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, false, false, + + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: false, + headerDebugAllowed: false, + addCallSignHeader: true, + bidAdjustments: bidAdjustments, + } + + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, config.AlternateBidderCodes{ Enabled: true, Bidders: map[string]config.AdapterAlternateBidderCodes{ @@ -2381,7 +2519,15 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) { bidAdjustments := map[string]float64{ string(openrtb_ext.BidderPubmatic): 2, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, false, false, + + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: false, + headerDebugAllowed: false, + addCallSignHeader: true, + bidAdjustments: bidAdjustments, + } + + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, config.AlternateBidderCodes{ Enabled: true, Bidders: map[string]config.AdapterAlternateBidderCodes{ diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index eec34964bf4..504b2cf359f 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -10,6 +10,7 @@ import ( "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/experiment/adscert" goCurrency "golang.org/x/text/currency" ) @@ -28,8 +29,8 @@ type validatedBidder struct { bidder AdaptedBidder } -func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustments map[string]float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool, alternateBidderCodes config.AlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) { - seatBids, errs := v.bidder.requestBid(ctx, bidderRequest, bidAdjustments, conversions, reqInfo, accountDebugAllowed, headerDebugAllowed, alternateBidderCodes) +func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes config.AlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) { + seatBids, errs := v.bidder.requestBid(ctx, bidderRequest, conversions, reqInfo, adsCertSigner, bidRequestOptions, alternateBidderCodes) for _, seatBid := range seatBids { if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid); len(validationErrors) > 0 { errs = append(errs, validationErrors...) diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go index 64afd63dd87..21ffa29837d 100644 --- a/exchange/bidder_validate_bids_test.go +++ b/exchange/bidder_validate_bids_test.go @@ -8,6 +8,7 @@ import ( "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/experiment/adscert" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -57,7 +58,13 @@ func TestAllValidBids(t *testing.T) { BidderName: openrtb_ext.BidderAppnexus, } bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0} - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustments, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false, config.AlternateBidderCodes{}) + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{}) assert.Len(t, seatBids, 1) assert.Len(t, seatBids[0].bids, 4) assert.Len(t, errs, 0) @@ -121,7 +128,13 @@ func TestAllBadBids(t *testing.T) { BidderName: openrtb_ext.BidderAppnexus, } bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0} - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustments, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false, config.AlternateBidderCodes{}) + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{}) assert.Len(t, seatBids, 1) assert.Len(t, seatBids[0].bids, 0) assert.Len(t, errs, 7) @@ -196,7 +209,13 @@ func TestMixedBids(t *testing.T) { BidderName: openrtb_ext.BidderAppnexus, } bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0} - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustments, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false, config.AlternateBidderCodes{}) + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{}) assert.Len(t, seatBids, 1) assert.Len(t, seatBids[0].bids, 3) assert.Len(t, errs, 5) @@ -317,8 +336,15 @@ func TestCurrencyBids(t *testing.T) { Cur: tc.brqCur, } bidderRequest := BidderRequest{BidRequest: request, BidderName: openrtb_ext.BidderAppnexus} + bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0} - seatBids, errs := bidder.requestBid(context.Background(), bidderRequest, bidAdjustments, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false, config.AlternateBidderCodes{}) + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + } + seatBids, errs := bidder.requestBid(context.Background(), bidderRequest, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{}) assert.Len(t, seatBids, 1) assert.Len(t, seatBids[0].bids, expectedValidBids) assert.Len(t, errs, expectedErrs) @@ -330,6 +356,6 @@ type mockAdaptedBidder struct { errorResponse []error } -func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustments map[string]float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool, alternateBidderCodes config.AlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) { +func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes config.AlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) { return b.bidResponse, b.errorResponse } diff --git a/exchange/exchange.go b/exchange/exchange.go index 5fc11d4e765..b0851af2142 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -18,6 +18,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/experiment/adscert" "github.com/prebid/prebid-server/firstpartydata" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/metrics" @@ -67,6 +68,7 @@ type exchange struct { categoriesFetcher stored_requests.CategoryFetcher bidIDGenerator BidIDGenerator hostSChainNode *openrtb2.SupplyChainNode + adsCertSigner adscert.Signer } // Container to pass out response ext data from the GetAllBids goroutines back into the main thread @@ -113,7 +115,7 @@ func (randomDeduplicateBidBooleanGenerator) Generate() bool { return rand.Intn(100) < 50 } -func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gdprPermsBuilder gdpr.PermissionsBuilder, tcf2CfgBuilder gdpr.TCF2ConfigBuilder, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher) Exchange { +func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gdprPermsBuilder gdpr.PermissionsBuilder, tcf2CfgBuilder gdpr.TCF2ConfigBuilder, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher, adsCertSigner adscert.Signer) Exchange { bidderToSyncerKey := map[string]string{} for bidder, syncer := range syncersByBidder { bidderToSyncerKey[bidder] = syncer.Key() @@ -144,6 +146,7 @@ func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid }, bidIDGenerator: &bidIDGenerator{cfg.GenerateBidID}, hostSChainNode: cfg.HostSChainNode, + adsCertSigner: adsCertSigner, } } @@ -280,7 +283,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } else { // List of bidders we have requests for. liveAdapters = listBiddersWithRequests(bidderRequests) - adapterBids, adapterExtra, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, r.Account.AlternateBidderCodes) + adapterBids, adapterExtra, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, r.Account.AlternateBidderCodes, requestExt.Prebid.Experiment) } var auc *auction @@ -488,7 +491,8 @@ func (e *exchange) getAllBids( accountDebugAllowed bool, globalPrivacyControlHeader string, headerDebugAllowed bool, - alternateBidderCodes config.AlternateBidderCodes) ( + alternateBidderCodes config.AlternateBidderCodes, + experiment *openrtb_ext.Experiment) ( map[openrtb_ext.BidderName]*pbsOrtbSeatBid, map[openrtb_ext.BidderName]*seatResponseExtra, bool) { // Set up pointers to the bid results @@ -517,7 +521,13 @@ func (e *exchange) getAllBids( reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader - seatBids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, bidAdjustments, conversions, &reqInfo, accountDebugAllowed, headerDebugAllowed, alternateBidderCodes) + bidReqOptions := bidRequestOptions{ + accountDebugAllowed: accountDebugAllowed, + headerDebugAllowed: headerDebugAllowed, + addCallSignHeader: isAdsCertEnabled(experiment, e.bidderInfo[string(bidderRequest.BidderName)]), + bidAdjustments: bidAdjustments, + } + seatBids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, conversions, &reqInfo, e.adsCertSigner, bidReqOptions, alternateBidderCodes) // Add in time reporting elapsed := time.Since(start) @@ -1201,3 +1211,9 @@ func buildStoredAuctionResponse(storedAuctionResponses map[string]json.RawMessag return adapterBids, liveAdapters, nil } + +func isAdsCertEnabled(experiment *openrtb_ext.Experiment, info config.BidderInfo) bool { + requestAdsCertEnabled := experiment != nil && experiment.AdsCert != nil && experiment.AdsCert.Enabled + bidderAdsCertEnabled := info.Experiment.AdsCert.Enabled + return requestAdsCertEnabled && bidderAdsCertEnabled +} diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 2a2a07014e3..9c22add4fda 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -27,6 +27,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/experiment/adscert" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/metrics" metricsConf "github.com/prebid/prebid-server/metrics/config" @@ -77,7 +78,7 @@ func TestNewExchange(t *testing.T) { cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, nilCategoryFetcher{}).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange) for _, bidderName := range knownAdapters { if _, ok := e.adapterMap[bidderName]; !ok { t.Errorf("NewExchange produced an Exchange without bidder %s", bidderName) @@ -135,7 +136,7 @@ func TestCharacterEscape(t *testing.T) { cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, nilCategoryFetcher{}).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange) // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs //liveAdapters []openrtb_ext.BidderName, @@ -1270,7 +1271,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), }.Builder - e := NewExchange(adapters, pbc, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, nilCategoryFetcher{}).(*exchange) + e := NewExchange(adapters, pbc, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange) // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs liveAdapters := []openrtb_ext.BidderName{bidderName} @@ -1634,7 +1635,7 @@ func TestBidResponseCurrency(t *testing.T) { cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, nilCategoryFetcher{}).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange) liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" @@ -1782,7 +1783,7 @@ func TestBidResponseImpExtInfo(t *testing.T) { cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), }.Builder - e := NewExchange(nil, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, nil, gdprPermsBuilder, tcf2ConfigBuilder, nil, nilCategoryFetcher{}).(*exchange) + e := NewExchange(nil, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, nil, gdprPermsBuilder, tcf2ConfigBuilder, nil, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange) liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" @@ -1891,7 +1892,7 @@ func TestRaceIntegration(t *testing.T) { cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), }.Builder - ex := NewExchange(adapters, &wellBehavedCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2CfgBuilder, currencyConverter, &nilCategoryFetcher{}).(*exchange) + ex := NewExchange(adapters, &wellBehavedCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2CfgBuilder, currencyConverter, &nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange) _, err = ex.HoldAuction(context.Background(), auctionRequest, &debugLog) if err != nil { t.Errorf("HoldAuction returned unexpected error: %v", err) @@ -1993,7 +1994,7 @@ func TestPanicRecovery(t *testing.T) { cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, nilCategoryFetcher{}).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange) chBids := make(chan *bidResponseWrapper, 1) panicker := func(bidderRequest BidderRequest, conversions currency.Conversions) { @@ -2074,7 +2075,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) { tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), }.Builder - e := NewExchange(adapters, &mockCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, categoriesFetcher).(*exchange) + e := NewExchange(adapters, &mockCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, categoriesFetcher, &adscert.NilSigner{}).(*exchange) e.adapterMap[openrtb_ext.BidderBeachfront] = panicingAdapter{} e.adapterMap[openrtb_ext.BidderAppnexus] = panicingAdapter{} @@ -4073,6 +4074,130 @@ func TestAuctionDebugEnabled(t *testing.T) { } +func TestPassExperimentConfigsToHoldAuction(t *testing.T) { + noBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) } + server := httptest.NewServer(http.HandlerFunc(noBidServer)) + defer server.Close() + + cfg := &config.Configuration{ + Adapters: make(map[string]config.Adapter, 1), + } + cfg.Adapters["appnexus"] = config.Adapter{ + Endpoint: "test.com", + } + biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info", cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) + if err != nil { + t.Fatal(err) + } + signer := MockSigner{} + + adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.NilMetricsEngine{}) + if adaptersErr != nil { + t.Fatalf("Error intializing adapters: %v", adaptersErr) + } + + currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + + gdprPermsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder + tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ + cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + }.Builder + + biddersInfo["appnexus"] = config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: true}}} + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, nilCategoryFetcher{}, &signer).(*exchange) + + // Define mock incoming bid requeset + mockBidRequest := &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id", + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + Ext: json.RawMessage(`{"appnexus": {"placementId": 1}}`), + }}, + Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + Ext: json.RawMessage(`{"prebid":{"experiment":{"adscert":{"enabled": true}}}}`), + } + + auctionRequest := AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: mockBidRequest}, + Account: config.Account{}, + UserSyncs: &emptyUsersync{}, + } + + debugLog := DebugLog{} + _, err = e.HoldAuction(context.Background(), auctionRequest, &debugLog) + + assert.NoError(t, err, "unexpected error occured") + assert.Equal(t, "test.com", signer.data, "incorrect signer data") +} + +func TestCallSignHeader(t *testing.T) { + type aTest struct { + description string + experiment openrtb_ext.Experiment + bidderInfo config.BidderInfo + expectedResult bool + } + var nilExperiment openrtb_ext.Experiment + + testCases := []aTest{ + { + description: "both experiment.adsCert enabled for request and for bidder ", + experiment: openrtb_ext.Experiment{AdsCert: &openrtb_ext.AdsCert{Enabled: true}}, + bidderInfo: config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: true}}}, + expectedResult: true, + }, + { + description: "experiment is not defined in request, bidder config adsCert enabled", + experiment: nilExperiment, + bidderInfo: config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: true}}}, + expectedResult: false, + }, + { + description: "experiment.adsCert is not defined in request, bidder config adsCert enabled", + experiment: openrtb_ext.Experiment{AdsCert: nil}, + bidderInfo: config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: true}}}, + expectedResult: false, + }, + { + description: "experiment.adsCert is disabled in request, bidder config adsCert enabled", + experiment: openrtb_ext.Experiment{AdsCert: &openrtb_ext.AdsCert{Enabled: false}}, + bidderInfo: config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: true}}}, + expectedResult: false, + }, + { + description: "experiment.adsCert is enabled in request, bidder config adsCert disabled", + experiment: openrtb_ext.Experiment{AdsCert: &openrtb_ext.AdsCert{Enabled: true}}, + bidderInfo: config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: false}}}, + expectedResult: false, + }, + { + description: "experiment.adsCert is disabled in request, bidder config adsCert disabled", + experiment: openrtb_ext.Experiment{AdsCert: &openrtb_ext.AdsCert{Enabled: false}}, + bidderInfo: config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: false}}}, + expectedResult: false, + }, + } + for _, test := range testCases { + result := isAdsCertEnabled(&test.experiment, test.bidderInfo) + assert.Equal(t, test.expectedResult, result, "incorrect result returned") + } + +} + +type MockSigner struct { + data string +} + +func (ms *MockSigner) Sign(destinationURL string, body []byte) (string, error) { + ms.data = destinationURL + return "mock data", nil +} + type exchangeSpec struct { GDPREnabled bool `json:"gdpr_enabled"` IncomingRequest exchangeRequest `json:"incomingRequest"` @@ -4154,11 +4279,11 @@ type validatingBidder struct { mockResponses map[string]bidderResponse } -func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustments map[string]float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool, alternateBidderCodes config.AlternateBidderCodes) (seatBids []*pbsOrtbSeatBid, errs []error) { +func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes config.AlternateBidderCodes) (seatBids []*pbsOrtbSeatBid, errs []error) { if expectedRequest, ok := b.expectations[string(bidderRequest.BidderName)]; ok { if expectedRequest != nil { - if !reflect.DeepEqual(expectedRequest.BidAdjustments, bidAdjustments) { - b.t.Errorf("%s: Bidder %s got wrong bid adjustment. Expected %v, got %v", b.fileName, bidderRequest.BidderName, expectedRequest.BidAdjustments, bidAdjustments) + if !reflect.DeepEqual(expectedRequest.BidAdjustments, bidRequestOptions.bidAdjustments) { + b.t.Errorf("%s: Bidder %s got wrong bid adjustment. Expected %v, got %v", b.fileName, bidderRequest.BidderName, expectedRequest.BidAdjustments, bidRequestOptions.bidAdjustments) } diffOrtbRequests(b.t, fmt.Sprintf("Request to %s in %s", string(bidderRequest.BidderName), b.fileName), &expectedRequest.OrtbRequest, bidderRequest.BidRequest) } @@ -4211,7 +4336,7 @@ type capturingRequestBidder struct { req *openrtb2.BidRequest } -func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustments map[string]float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool, alternateBidderCodes config.AlternateBidderCodes) (seatBid []*pbsOrtbSeatBid, errs []error) { +func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes config.AlternateBidderCodes) (seatBid []*pbsOrtbSeatBid, errs []error) { b.req = bidderRequest.BidRequest return []*pbsOrtbSeatBid{{}}, nil } @@ -4318,7 +4443,7 @@ func (e *emptyUsersync) HasAnyLiveSyncs() bool { type panicingAdapter struct{} -func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustments map[string]float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool, alternateBidderCodes config.AlternateBidderCodes) (posb []*pbsOrtbSeatBid, errs []error) { +func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes config.AlternateBidderCodes) (posb []*pbsOrtbSeatBid, errs []error) { panic("Panic! Panic! The world is ending!") } diff --git a/experiment/adscert/inprocesssigner.go b/experiment/adscert/inprocesssigner.go new file mode 100644 index 00000000000..604287f9ed6 --- /dev/null +++ b/experiment/adscert/inprocesssigner.go @@ -0,0 +1,42 @@ +package adscert + +import ( + "crypto/rand" + "github.com/IABTechLab/adscert/pkg/adscert/api" + "github.com/IABTechLab/adscert/pkg/adscert/discovery" + "github.com/IABTechLab/adscert/pkg/adscert/signatory" + "github.com/benbjohnson/clock" + "github.com/prebid/prebid-server/config" + "time" +) + +// inProcessSigner holds the signatory to add adsCert header to requests using in process go library +type inProcessSigner struct { + signatory signatory.AuthenticatedConnectionsSignatory +} + +// Sign adds adsCert header to requests using in process go library +func (ips *inProcessSigner) Sign(destinationURL string, body []byte) (string, error) { + req := &api.AuthenticatedConnectionSignatureRequest{ + RequestInfo: createRequestInfo(destinationURL, body), + } + signatureResponse, err := ips.signatory.SignAuthenticatedConnection(req) + if err != nil { + return "", err + } + return getSignatureMessage(signatureResponse) +} + +func newInProcessSigner(inProcessSignerConfig config.AdsCertInProcess) (*inProcessSigner, error) { + return &inProcessSigner{ + signatory: signatory.NewLocalAuthenticatedConnectionsSignatory( + inProcessSignerConfig.Origin, + rand.Reader, + clock.New(), + discovery.NewDefaultDnsResolver(), + discovery.NewDefaultDomainStore(), + time.Duration(inProcessSignerConfig.DNSCheckIntervalInSeconds)*time.Second, + time.Duration(inProcessSignerConfig.DNSRenewalIntervalInSeconds)*time.Second, + []string{inProcessSignerConfig.PrivateKey}), + }, nil +} diff --git a/experiment/adscert/inprocesssigner_test.go b/experiment/adscert/inprocesssigner_test.go new file mode 100644 index 00000000000..603eb698be9 --- /dev/null +++ b/experiment/adscert/inprocesssigner_test.go @@ -0,0 +1,50 @@ +package adscert + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestInProcessSigner(t *testing.T) { + type aTest struct { + desc string + generateError bool + operationStatusOk bool + } + testCases := []aTest{ + { + desc: "generate signer error", + generateError: true, + operationStatusOk: false, + }, + { + desc: "generate valid response without signature operation error", + generateError: false, + operationStatusOk: true, + }, + { + desc: "generate valid response with signature operation error", + generateError: false, + operationStatusOk: false, + }, + } + + for _, test := range testCases { + signatory := &MockLocalAuthenticatedConnectionsSignatory{ + returnError: test.generateError, + operationStatusOk: test.operationStatusOk, + } + signer := &inProcessSigner{signatory: signatory} + signatureMessage, err := signer.Sign("http://test.com", []byte{}) + if test.generateError { + assert.EqualError(t, err, "Test error", "incorrect error returned for test: %s", test.desc) + } else { + if test.operationStatusOk { + assert.NoError(t, err, "incorrect result for test: %s", test.desc) + assert.Equal(t, "Success", signatureMessage, "incorrect message returned for test : %s", test.desc) + } else { + assert.EqualError(t, err, "error signing request: SIGNATURE_OPERATION_STATUS_UNDEFINED", "incorrect error type returned for test: %s", test.desc) + } + } + } +} diff --git a/experiment/adscert/remotesigner.go b/experiment/adscert/remotesigner.go new file mode 100644 index 00000000000..3c9479560b2 --- /dev/null +++ b/experiment/adscert/remotesigner.go @@ -0,0 +1,44 @@ +package adscert + +import ( + "fmt" + "github.com/IABTechLab/adscert/pkg/adscert/api" + "github.com/IABTechLab/adscert/pkg/adscert/signatory" + "github.com/prebid/prebid-server/config" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "time" +) + +// remoteSigner holds the signatory to add adsCert header to requests using remote signing server +type remoteSigner struct { + signatory signatory.AuthenticatedConnectionsSignatory +} + +// Sign adds adsCert header to requests using remote signing server +func (rs *remoteSigner) Sign(destinationURL string, body []byte) (string, error) { + signatureResponse, err := rs.signatory.SignAuthenticatedConnection( + &api.AuthenticatedConnectionSignatureRequest{ + RequestInfo: createRequestInfo(destinationURL, []byte(body)), + }) + if err != nil { + return "", err + } + return getSignatureMessage(signatureResponse) +} + +func newRemoteSigner(remoteSignerConfig config.AdsCertRemote) (*remoteSigner, error) { + // Establish the gRPC connection that the client will use to connect to the + // signatory server. Secure connections are not implemented at this time. + opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())} + conn, err := grpc.Dial(remoteSignerConfig.Url, opts...) + if err != nil { + return nil, fmt.Errorf("failed to dial remote signer: %v", err) + } + + clientOpts := &signatory.AuthenticatedConnectionsSignatoryClientOptions{ + Timeout: time.Duration(remoteSignerConfig.SigningTimeoutMs) * time.Millisecond} + signatoryClient := signatory.NewAuthenticatedConnectionsSignatoryClient(conn, clientOpts) + return &remoteSigner{signatory: signatoryClient}, nil + +} diff --git a/experiment/adscert/remotesigner_test.go b/experiment/adscert/remotesigner_test.go new file mode 100644 index 00000000000..7cf56f6d826 --- /dev/null +++ b/experiment/adscert/remotesigner_test.go @@ -0,0 +1,50 @@ +package adscert + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestRemoteSigner(t *testing.T) { + type aTest struct { + desc string + generateError bool + operationStatusOk bool + } + testCases := []aTest{ + { + desc: "generate signer error", + generateError: true, + operationStatusOk: false, + }, + { + desc: "generate valid response without signature operation error", + generateError: false, + operationStatusOk: true, + }, + { + desc: "generate valid response with signature operation error", + generateError: false, + operationStatusOk: false, + }, + } + + for _, test := range testCases { + signatory := &MockLocalAuthenticatedConnectionsSignatory{ + returnError: test.generateError, + operationStatusOk: test.operationStatusOk, + } + signer := &remoteSigner{signatory: signatory} + signatureMessage, err := signer.Sign("http://test.com", []byte{}) + if test.generateError { + assert.EqualError(t, err, "Test error", "incorrect error returned for test: %s", test.desc) + } else { + if test.operationStatusOk { + assert.NoError(t, err, "incorrect result for test: %s", test.desc) + assert.Equal(t, "Success", signatureMessage, "incorrect message returned for test: %s", test.desc) + } else { + assert.EqualError(t, err, "error signing request: SIGNATURE_OPERATION_STATUS_UNDEFINED", "incorrect error type returned for test: %s", test.desc) + } + } + } +} diff --git a/experiment/adscert/signer.go b/experiment/adscert/signer.go new file mode 100644 index 00000000000..352aa004b6a --- /dev/null +++ b/experiment/adscert/signer.go @@ -0,0 +1,49 @@ +package adscert + +import ( + "fmt" + "github.com/IABTechLab/adscert/pkg/adscert/api" + "github.com/IABTechLab/adscert/pkg/adscert/signatory" + "github.com/prebid/prebid-server/config" +) + +const SignHeader = "X-Ads-Cert-Auth" + +// Signer represents interface to access request Ads Cert signing functionality +type Signer interface { + Sign(destinationURL string, body []byte) (string, error) +} + +type NilSigner struct { +} + +func (ns *NilSigner) Sign(destinationURL string, body []byte) (string, error) { + return "", nil +} + +func NewAdCertsSigner(experimentAdCertsConfig config.ExperimentAdsCert) (Signer, error) { + if experimentAdCertsConfig.Mode == config.AdCertsSignerModeInprocess { + return newInProcessSigner(experimentAdCertsConfig.InProcess) + } + if experimentAdCertsConfig.Mode == config.AdCertsSignerModeRemote { + return newRemoteSigner(experimentAdCertsConfig.Remote) + } + return &NilSigner{}, nil +} + +func createRequestInfo(destinationURL string, body []byte) *api.RequestInfo { + // The RequestInfo proto contains details about the individual ad request + // being signed. A SetRequestInfo helper function derives a hash of the + // destination URL and body, setting these value on the RequestInfo message. + reqInfo := &api.RequestInfo{} + signatory.SetRequestInfo(reqInfo, destinationURL, body) + return reqInfo +} + +func getSignatureMessage(signatureResponse *api.AuthenticatedConnectionSignatureResponse) (string, error) { + if signatureResponse.GetSignatureOperationStatus() == api.SignatureOperationStatus_SIGNATURE_OPERATION_STATUS_OK { + signatureMessage := signatureResponse.RequestInfo.SignatureInfo[0].SignatureMessage + return signatureMessage, nil + } + return "", fmt.Errorf("error signing request: %s", signatureResponse.GetSignatureOperationStatus()) +} diff --git a/experiment/adscert/signer_test.go b/experiment/adscert/signer_test.go new file mode 100644 index 00000000000..d6d02175d95 --- /dev/null +++ b/experiment/adscert/signer_test.go @@ -0,0 +1,54 @@ +package adscert + +import ( + "errors" + "github.com/IABTechLab/adscert/pkg/adscert/api" + "github.com/prebid/prebid-server/config" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNilSigner(t *testing.T) { + config := config.ExperimentAdsCert{Mode: "off", InProcess: config.AdsCertInProcess{Origin: ""}, Remote: config.AdsCertRemote{Url: ""}} + signer, err := NewAdCertsSigner(config) + assert.NoError(t, err, "error should not be returned if not inprocess nor remote signer defined, NilSigner should be returned instead") + message, err := signer.Sign("test.com", nil) + assert.NoError(t, err, "NilSigner should not return an error") + assert.Equal(t, "", message, "incorrect message returned NilSigner") +} + +func TestNilSignerForAdsCertDisabled(t *testing.T) { + config := config.ExperimentAdsCert{Mode: "off", InProcess: config.AdsCertInProcess{Origin: ""}, Remote: config.AdsCertRemote{Url: ""}} + signer, err := NewAdCertsSigner(config) + assert.NoError(t, err, "error should not be returned if AdsCerts feature is disabled") + message, err := signer.Sign("test.com", nil) + assert.NoError(t, err, "NilSigner should not return an error") + assert.Equal(t, "", message, "incorrect message returned NilSigner") +} + +type MockLocalAuthenticatedConnectionsSignatory struct { + returnError bool + operationStatusOk bool +} + +func (ips *MockLocalAuthenticatedConnectionsSignatory) SignAuthenticatedConnection(request *api.AuthenticatedConnectionSignatureRequest) (*api.AuthenticatedConnectionSignatureResponse, error) { + if ips.returnError { + return nil, errors.New("Test error") + } + response := &api.AuthenticatedConnectionSignatureResponse{ + RequestInfo: &api.RequestInfo{ + SignatureInfo: []*api.SignatureInfo{ + {SignatureMessage: "Success"}, + }, + }, + } + if ips.operationStatusOk { + response.SignatureOperationStatus = api.SignatureOperationStatus_SIGNATURE_OPERATION_STATUS_OK + } else { + response.SignatureOperationStatus = api.SignatureOperationStatus_SIGNATURE_OPERATION_STATUS_UNDEFINED + } + return response, nil +} +func (ips *MockLocalAuthenticatedConnectionsSignatory) VerifyAuthenticatedConnection(request *api.AuthenticatedConnectionVerificationRequest) (*api.AuthenticatedConnectionVerificationResponse, error) { + return nil, nil +} diff --git a/go.mod b/go.mod index eef1e244ee5..91faf67ef73 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.16 require ( github.com/DATA-DOG/go-sqlmock v1.5.0 + github.com/IABTechLab/adscert v0.34.0 github.com/NYTimes/gziphandler v1.1.1 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/benbjohnson/clock v1.3.0 @@ -32,6 +33,8 @@ require ( github.com/yudai/pp v2.0.1+incompatible // indirect golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 golang.org/x/text v0.3.7 + google.golang.org/grpc v1.46.2 + google.golang.org/protobuf v1.28.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 73a65c62ccb..d55a80cdbe6 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,7 @@ cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aD cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -59,6 +60,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/IABTechLab/adscert v0.34.0 h1:UNM2gMfRPGUbv3KDiLJmy2ajaVCfF3jWqgVKkz8wBu8= +github.com/IABTechLab/adscert v0.34.0/go.mod h1:pCLd3Up1kfTrH6kYFUGGeavxIc1f6Tvvj8yJeFRb7mA= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -75,6 +78,7 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aws/aws-sdk-go v1.36.29/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -85,6 +89,7 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -107,10 +112,12 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coocood/freecache v1.2.1 h1:/v1CqMq45NFH9mp/Pt142reundeBM0dVUD3osQBeu/U= github.com/coocood/freecache v1.2.1/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -126,15 +133,19 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -144,11 +155,13 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -188,6 +201,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -227,6 +241,7 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/tink/go v1.6.1/go.mod h1:IGW53kTgag+st5yPhKKwJ6u2l+SSp5/v9XF7spovjlY= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -238,40 +253,60 @@ github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= +github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -299,6 +334,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -316,18 +353,24 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -342,6 +385,7 @@ github.com/mxmCherry/openrtb/v16 v16.0.0-alpha.2/go.mod h1:k+21QiTXLLjjXCroputuQ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.1 h1:foqVmeWDD6yYpK+Yz3fHyNIxFYNxswxqNFjSKe+vI54= @@ -352,14 +396,17 @@ github.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug= github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -402,7 +449,11 @@ github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBO github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= @@ -411,14 +462,19 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -432,6 +488,7 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= @@ -454,8 +511,11 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= +go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU= go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -470,14 +530,20 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -514,9 +580,11 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -558,6 +626,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -602,10 +671,13 @@ golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -667,11 +739,13 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -688,6 +762,7 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -776,6 +851,7 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= @@ -791,6 +867,7 @@ google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= @@ -808,6 +885,7 @@ google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -867,6 +945,8 @@ google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= @@ -883,10 +963,13 @@ google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I= google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -910,9 +993,11 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -931,6 +1016,7 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= @@ -939,8 +1025,10 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 04340438215..67cad55b7c7 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -57,6 +57,17 @@ type ExtRequestPrebid struct { CurrencyConversions *ExtRequestCurrency `json:"currency,omitempty"` BidderConfigs []BidderConfig `json:"bidderconfig,omitempty"` + Experiment *Experiment `json:"experiment,omitempty"` +} + +// Experiment defines if experimental features are available for the request +type Experiment struct { + AdsCert *AdsCert `json:"adscert,omitempty"` +} + +// AdsCert defines if Call Sign feature is enabled for request +type AdsCert struct { + Enabled bool `json:"enabled,omitempty"` } type BidderConfig struct { diff --git a/router/router.go b/router/router.go index abd407de4ce..f2ec00479f0 100644 --- a/router/router.go +++ b/router/router.go @@ -20,6 +20,7 @@ import ( "github.com/prebid/prebid-server/endpoints/openrtb2" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/exchange" + "github.com/prebid/prebid-server/experiment/adscert" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/metrics" metricsConf "github.com/prebid/prebid-server/metrics/config" @@ -210,8 +211,12 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R errs := errortypes.NewAggregateError("Failed to initialize adapters", adaptersErrs) return nil, errs } + adsCertSigner, err := adscert.NewAdCertsSigner(cfg.Experiment.AdCerts) + if err != nil { + glog.Fatalf("Failed to create ads cert signer: %v", err) + } - theExchange := exchange.NewExchange(adapters, cacheClient, cfg, syncersByBidder, r.MetricsEngine, bidderInfos, gdprPermsBuilder, tcf2CfgBuilder, rateConvertor, categoriesFetcher) + theExchange := exchange.NewExchange(adapters, cacheClient, cfg, syncersByBidder, r.MetricsEngine, bidderInfos, gdprPermsBuilder, tcf2CfgBuilder, rateConvertor, categoriesFetcher, adsCertSigner) var uuidGenerator uuidutil.UUIDRandomGenerator openrtbEndpoint, err := openrtb2.NewEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, storedRespFetcher) if err != nil {